From 7b5c61d6712e0a607136603d21231b71b9865fc9 Mon Sep 17 00:00:00 2001 From: Andreas Tsaridas Date: Tue, 8 Sep 2026 10:37:29 +0000 Subject: [PATCH 1/6] build: add multi-stage Dockerfile for libtorrent backend Mirrors the release.yml CI build (Debian/Ubuntu base, libtorrent 2.1.1 built from source, --features libtorrent --no-default-features) using cargo-chef for layer caching. Runs the server binary headless via --no-tray since the release build defaults to tray mode. --- .dockerignore | 13 +++++++++ Dockerfile | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..c8107405 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +/target +/target-patched +/libtorrent-downloads +/rqbit-downloads +/vcpkg +/vcpkg_installed +err.txt +/System.Collections.Hashtable.Root/ +.git +docs +icons +scripts/tests +*.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b403b8b8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +# syntax=docker/dockerfile:1 +# +# Multi-stage build for stream-server with the libtorrent backend. +# Mirrors the "check"/"build-linux" jobs in .github/workflows/release.yml: +# same base OS family (Debian/Ubuntu), same libtorrent version, same +# cargo feature flags, so the image matches what CI already validates. + +######################################## +# Stage 1: cargo-chef base +######################################## +FROM rust:1-bookworm AS chef +RUN cargo install cargo-chef --locked +WORKDIR /app + +######################################## +# Stage 2: dependency recipe (for Docker layer caching) +######################################## +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +######################################## +# Stage 3: builder +######################################## +FROM chef AS builder + +ENV DEBIAN_FRONTEND=noninteractive \ + LIBTORRENT_STATIC=1 \ + PKG_CONFIG_PATH=/usr/local/lib/pkgconfig + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake curl pkg-config \ + libssl-dev libboost-dev libclang-dev \ + libgtk-3-dev libayatana-appindicator3-dev \ + && rm -rf /var/lib/apt/lists/* + +# Build libtorrent-rasterbar 2.1.1 from source, statically, the same way CI does. +COPY .github/scripts/install-libtorrent.sh .github/scripts/install-libtorrent.sh +RUN bash .github/scripts/install-libtorrent.sh + +# Cache dependency compilation separately from application code. +COPY --from=planner /app/recipe.json recipe.json +RUN cargo chef cook --release --recipe-path recipe.json --features libtorrent --no-default-features + +COPY . . +RUN cargo build --release --features libtorrent --no-default-features + +######################################## +# Stage 4: runtime +######################################## +FROM debian:bookworm-slim AS runtime + +ENV DEBIAN_FRONTEND=noninteractive \ + RUST_LOG=info + +# server links against the desktop-integration stack (tray icon / settings GUI) +# on all cfg(unix/windows/macos) targets, so the runtime image needs the +# shared libraries those bring in even though it starts headless. Installing +# the -dev metapackages (rather than hand-picking .so names) guarantees every +# transitive runtime library apt would install for the equivalent build +# dependency is present too, avoiding "error while loading shared libraries" +# at startup. +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates ffmpeg \ + libssl-dev libboost-dev libgtk-3-dev libayatana-appindicator3-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/target/release/server /usr/local/bin/stream-server + +# HTTP API / streaming, HTTPS. BitTorrent peer traffic uses an +# ephemeral/DHT-negotiated port by default -- run with --network=host or +# publish additional ports if you pin one via the /settings API. +EXPOSE 11470/tcp 12470/tcp + +VOLUME ["/root/.config/stremio-server", "/root/.cache/stremio-server"] + +# --no-tray forces the headless code path (no tray/GUI init), since the +# release binary otherwise defaults to tray mode, which requires a display. +ENTRYPOINT ["/usr/local/bin/stream-server", "--no-tray"] From 964fb3aa3f8293dd4bb9cf0812182dc6d102f5c1 Mon Sep 17 00:00:00 2001 From: Andreas Tsaridas Date: Tue, 8 Sep 2026 11:14:02 +0000 Subject: [PATCH 2/6] fix(docker): keep icons/ in build context, document Docker usage tray.rs embeds icons/icon_48.png via include_bytes! at build time, so excluding icons/ in .dockerignore broke the build. Also expose/publish the default BitTorrent listen range (42000-42010) instead of a vague "ephemeral port" note, based on what /settings actually reports. Verified end-to-end on the target host: docker build succeeds, the container starts headless, and /heartbeat, /settings, /stats.json all respond correctly with the libtorrent backend active. Document Docker usage, how to point a Stremio client at the server (there's no bundled web UI), and how to configure libtorrent via the /settings API in the README. --- .dockerignore | 1 - Dockerfile | 7 +++--- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/.dockerignore b/.dockerignore index c8107405..b3d6fb60 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,6 +8,5 @@ err.txt /System.Collections.Hashtable.Root/ .git docs -icons scripts/tests *.md diff --git a/Dockerfile b/Dockerfile index b403b8b8..17fcb5ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -67,10 +67,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ COPY --from=builder /app/target/release/server /usr/local/bin/stream-server -# HTTP API / streaming, HTTPS. BitTorrent peer traffic uses an -# ephemeral/DHT-negotiated port by default -- run with --network=host or -# publish additional ports if you pin one via the /settings API. -EXPOSE 11470/tcp 12470/tcp +# HTTP API / streaming, HTTPS, and the default BitTorrent listen range +# (btListenInterfaces, configurable via /settings). +EXPOSE 11470/tcp 12470/tcp 42000-42010/tcp 42000-42010/udp VOLUME ["/root/.config/stremio-server", "/root/.cache/stremio-server"] diff --git a/README.md b/README.md index c1d26bac..525952e7 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,66 @@ The server starts on `http://localhost:11470` by default (compatible with standa --- +## 🐳 Docker + +A multi-stage `Dockerfile` builds the `libtorrent` backend the same way CI does (libtorrent 2.1.1 from source, `--features libtorrent --no-default-features`): + +```bash +docker build -t stream-server:libtorrent . + +docker run -d --name stream-server \ + -p 11470:11470 \ + -p 42000-42010:42000-42010/tcp \ + -p 42000-42010:42000-42010/udp \ + -v stream-server-config:/root/.config/stremio-server \ + -v stream-server-cache:/root/.cache/stremio-server \ + stream-server:libtorrent +``` + +- The container starts headless automatically (equivalent to `stream-server --no-tray`) — no display server required. +- Mount `/root/.config/stremio-server` (settings, HTTPS certs, logs) and `/root/.cache/stremio-server` (downloads/cache) as volumes so they survive container recreation. +- BitTorrent peer traffic defaults to ports `42000-42010` (TCP+UDP, `btListenInterfaces` in `/settings`). Publish that range (or run with `--network host`) for good peer connectivity. +- `RUST_LOG` is set to `info` in the image; override with `-e RUST_LOG=debug` for more verbose logs. + +This has been built and smoke-tested end-to-end (image build + container run + `/heartbeat`, `/settings`, `/stats.json`) on an x86_64 Fedora/Docker host. + +## 🖥️ Accessing & Configuring the UI + +Stream Server has **no bundled web UI of its own** — it's a drop-in backend that a Stremio client (web, desktop, or mobile app) talks to, the same way it talks to Stremio's official `server.js`. To use it: + +1. Open the [Stremio web app](https://web.stremio.com) or a desktop/mobile Stremio client. +2. Go to **Settings → Streaming Server**. +3. Set **Server URL** to where this container/binary is reachable, e.g. `http://192.168.1.5:11470/` (swap in your host's IP or hostname). +4. Stremio should report the server as connected; playing a torrent stream will now route through this server instead of Stremio's built-in one. + +Useful endpoints for checking the server directly (from any machine that can reach it): + +```bash +curl http://192.168.1.5:11470/heartbeat # health check +curl http://192.168.1.5:11470/stats.json # server + torrent stats +curl http://192.168.1.5:11470/settings # current settings, including BitTorrent options +``` + +If desktop-app config is what you want instead, `settings-gui` (built alongside `server` on Windows/macOS/Linux desktop builds) provides a native settings window — not applicable to the headless Docker image above. + +## ⚙️ Configuring libtorrent + +BitTorrent/libtorrent behavior is configured at runtime via the same `/settings` API used by the UI above, and persisted to `settings.json` in the config directory (`~/.config/stremio-server/settings.json` on Linux, or the `stream-server-config` volume in Docker) — no rebuild needed. Full option reference: [docs/bittorrent-settings.md](docs/bittorrent-settings.md). + +```bash +# View current settings +curl http://192.168.1.5:11470/settings + +# Update just the keys you want to change +curl -X POST http://192.168.1.5:11470/settings \ + -H "Content-Type: application/json" \ + -d '{"btEnableDht": false, "btEncryptionMode": "require"}' +``` + +Key options: `btEnableDht`, `btEnablePex`, `btEnableLsd` (peer discovery), `btEncryptionMode` (`allow`/`require`/`disable`), `btAnonymousMode`, `btAllowMultipleConnectionsPerIp`, `btValidateHttpsTrackers`, `btSsrfMitigation`. + +--- + ## 🔧 Build Instructions
From 24576d6dc699cb9dfa4c828904d406eadd366e5a Mon Sep 17 00:00:00 2001 From: Andreas Tsaridas Date: Tue, 8 Sep 2026 11:40:30 +0000 Subject: [PATCH 3/6] change port --- server/src/lib.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index 69db2f91..d8488d48 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -112,10 +112,26 @@ impl ServerConfig { } pub fn binary_default() -> Self { + // Overridable so multiple instances (or instances sharing a host with + // other services on the default ports) can run with --network host, + // where Docker can no longer remap ports for us. + let http_port = std::env::var("STREAM_SERVER_HTTP_PORT") + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or(DEFAULT_HTTP_PORT); + let https_port = std::env::var("STREAM_SERVER_HTTPS_PORT") + .ok() + .and_then(|v| v.parse::().ok()) + .unwrap_or(DEFAULT_HTTPS_PORT); + let public_base_url = std::env::var("STREAM_SERVER_PUBLIC_BASE_URL") + .ok() + .filter(|v| !v.is_empty()) + .unwrap_or_else(|| format!("http://127.0.0.1:{http_port}")); + Self { - http_addr: SocketAddr::from(([0, 0, 0, 0], DEFAULT_HTTP_PORT)), - https_addr: Some(SocketAddr::from(([0, 0, 0, 0], DEFAULT_HTTPS_PORT))), - public_base_url: Some(format!("http://127.0.0.1:{DEFAULT_HTTP_PORT}")), + http_addr: SocketAddr::from(([0, 0, 0, 0], http_port)), + https_addr: Some(SocketAddr::from(([0, 0, 0, 0], https_port))), + public_base_url: Some(public_base_url), config_dir: None, cache_dir: None, use_tui: false, From 625a7f9a7d7c31d9959f8d81e88688935d8717e2 Mon Sep 17 00:00:00 2001 From: Andreas Tsaridas Date: Tue, 8 Sep 2026 11:44:22 +0000 Subject: [PATCH 4/6] docs: document --network host deployment, port overrides, and TUI Cover the env-var port overrides (STREAM_SERVER_HTTP_PORT/HTTPS_PORT/ PUBLIC_BASE_URL) needed for --network host when the default ports are already taken, the built-in ratatui TUI stats dashboard (--tui, attach via docker attach), and a fast-playback /settings tuning example. This matches an actual deployment on the target host: replaced an untracked ad hoc stream-server:dev container (bridge networking + nginx port-remap trick) with this Dockerfile's build running --network host, reusing its existing data volume, with GPU passthrough for hardware transcoding and the TUI enabled. --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 525952e7..5a86ebf6 100644 --- a/README.md +++ b/README.md @@ -127,22 +127,61 @@ docker run -d --name stream-server \ ``` - The container starts headless automatically (equivalent to `stream-server --no-tray`) — no display server required. -- Mount `/root/.config/stremio-server` (settings, HTTPS certs, logs) and `/root/.cache/stremio-server` (downloads/cache) as volumes so they survive container recreation. +- Mount `/root/.config/stremio-server` (settings, HTTPS certs, logs) and `/root/.cache/stremio-server` (downloads/cache) as volumes so they survive container recreation. `HOME`/`XDG_CONFIG_HOME`/`XDG_CACHE_HOME`/`XDG_DATA_HOME` are also honored if you'd rather point everything at one mounted directory. - BitTorrent peer traffic defaults to ports `42000-42010` (TCP+UDP, `btListenInterfaces` in `/settings`). Publish that range (or run with `--network host`) for good peer connectivity. - `RUST_LOG` is set to `info` in the image; override with `-e RUST_LOG=debug` for more verbose logs. +- Pass `--tui` as an extra image argument (with `docker run -it`, or `-d -it` to attach later) for the built-in terminal stats dashboard — see [Accessing & Configuring the UI](#-accessing--configuring-the-ui). -This has been built and smoke-tested end-to-end (image build + container run + `/heartbeat`, `/settings`, `/stats.json`) on an x86_64 Fedora/Docker host. +### `--network host` + +The default ports (`11470`/`12470`) or BitTorrent range (`42000-42010`) may already be taken by something else on the host (another stream-server instance, Stremio's own `server.js`, etc.) — this is common if you're running this alongside other services. Since `--network host` means Docker can no longer remap ports for you, the ports are overridable via environment variables: + +| Variable | Default | Purpose | +| --- | --- | --- | +| `STREAM_SERVER_HTTP_PORT` | `11470` | HTTP API/streaming port | +| `STREAM_SERVER_HTTPS_PORT` | `12470` | HTTPS port (only serves if certs exist in the config dir) | +| `STREAM_SERVER_PUBLIC_BASE_URL` | `http://127.0.0.1:` | Base URL the server reports about itself, e.g. in `/settings` | + +```bash +docker run -d -it --name stream-server \ + --network host \ + --restart unless-stopped \ + -v stream-server-data:/data \ + -e HOME=/data -e XDG_CONFIG_HOME=/data/config -e XDG_CACHE_HOME=/data/cache -e XDG_DATA_HOME=/data/share \ + -e STREAM_SERVER_HTTP_PORT=11472 -e STREAM_SERVER_HTTPS_PORT=12472 \ + -e STREAM_SERVER_PUBLIC_BASE_URL=http://192.168.1.5:11472 \ + --device /dev/dri:/dev/dri \ + stream-server:libtorrent --tui +``` + +`--device /dev/dri:/dev/dri` passes through the host GPU for hardware-accelerated FFmpeg transcoding, if available. `-it` (kept even when running detached with `-d`) allocates a pty so `docker attach` can show the `--tui` dashboard later without disrupting the running server (see below). + +This exact configuration has been built and deployed end-to-end (image build, container run, `/heartbeat`, `/settings` tuning, and TUI attach all verified) on an x86_64 Fedora/Docker host running alongside other, unrelated streaming services. ## 🖥️ Accessing & Configuring the UI -Stream Server has **no bundled web UI of its own** — it's a drop-in backend that a Stremio client (web, desktop, or mobile app) talks to, the same way it talks to Stremio's official `server.js`. To use it: +Stream Server has **no bundled web UI** — it's a drop-in backend that a Stremio client (web, desktop, or mobile app) talks to, the same way it talks to Stremio's official `server.js`. To use it: 1. Open the [Stremio web app](https://web.stremio.com) or a desktop/mobile Stremio client. 2. Go to **Settings → Streaming Server**. -3. Set **Server URL** to where this container/binary is reachable, e.g. `http://192.168.1.5:11470/` (swap in your host's IP or hostname). +3. Set **Server URL** to where this container/binary is reachable, e.g. `http://192.168.1.5:11470/` (swap in your host's IP/port). 4. Stremio should report the server as connected; playing a torrent stream will now route through this server instead of Stremio's built-in one. -Useful endpoints for checking the server directly (from any machine that can reach it): +It does have a **built-in terminal (TUI) stats dashboard** — a live view of active engines/torrents and files, built with `ratatui`. Start the binary/container with `--tui` (alongside `--no-tray` for headless/Docker use) to enable it: + +```bash +# Native +stream-server --no-tray --tui + +# Docker: run with -it (even when backgrounded with -d) so a pty exists to attach to +docker run -d -it --name stream-server ... stream-server:libtorrent --tui +docker attach stream-server # view the live dashboard +# detach without stopping the container: Ctrl+P, Ctrl+Q +``` + +The TUI needs a real terminal (it's a no-op, non-fatal, if none is attached), so it doesn't fit a plain `docker run -d` without `-it`. + +For remote/scripted access instead, use the JSON endpoints directly: ```bash curl http://192.168.1.5:11470/heartbeat # health check @@ -154,7 +193,7 @@ If desktop-app config is what you want instead, `settings-gui` (built alongside ## ⚙️ Configuring libtorrent -BitTorrent/libtorrent behavior is configured at runtime via the same `/settings` API used by the UI above, and persisted to `settings.json` in the config directory (`~/.config/stremio-server/settings.json` on Linux, or the `stream-server-config` volume in Docker) — no rebuild needed. Full option reference: [docs/bittorrent-settings.md](docs/bittorrent-settings.md). +BitTorrent/libtorrent behavior is configured at runtime via the same `/settings` API used above, and persisted to `settings.json` in the config directory (`~/.config/stremio-server/settings.json` on Linux, or wherever your config volume/`XDG_CONFIG_HOME` points in Docker) — no rebuild needed. Full option reference: [docs/bittorrent-settings.md](docs/bittorrent-settings.md). ```bash # View current settings @@ -168,6 +207,32 @@ curl -X POST http://192.168.1.5:11470/settings \ Key options: `btEnableDht`, `btEnablePex`, `btEnableLsd` (peer discovery), `btEncryptionMode` (`allow`/`require`/`disable`), `btAnonymousMode`, `btAllowMultipleConnectionsPerIp`, `btValidateHttpsTrackers`, `btSsrfMitigation`. +### Tuning for playback speed + +The defaults are already reasonable, but for a dedicated streaming box these knobs matter most: + +```bash +curl -X POST http://192.168.1.5:11470/settings \ + -H "Content-Type: application/json" \ + -d '{ + "btMaxConnections": 1200, + "btMinPeersForStable": 3, + "btAllowMultipleConnectionsPerIp": true, + "btEncryptionMode": "allow", + "btDownloadSpeedSoftLimit": 0, + "btDownloadSpeedHardLimit": 0, + "seedingEnabled": true + }' +``` + +- `btMaxConnections` (up to `MAX_EFFECTIVE_BT_CONNECTIONS` = 1200, see `enginefs::backend::mod`): more simultaneous peer slots to source pieces from. +- `btMinPeersForStable`: lower means the engine reports "stable" (and playback can start) with fewer peers connected — trades a little early-playback smoothness for a faster start. +- `btEncryptionMode: "allow"` (not `"require"`/`"disable"`): accepts both encrypted and plaintext peers, maximizing the usable peer pool. Only `"require"` if your ISP throttles unencrypted BitTorrent traffic. +- `btDownloadSpeedSoftLimit`/`btDownloadSpeedHardLimit: 0`: unlimited. +- `seedingEnabled: true`: keeps seeding after download completes, which improves download speed too via tit-for-tat reciprocity with peers. +- Beyond `/settings`, run with `--network host` and publish/forward the `btListenInterfaces` port range — libtorrent's UPnP/NAT-PMP (always on) can then successfully port-map with your router for real incoming connections, which matters more for peer availability than any single setting above. +- Don't set `btSsrfMitigation`/`btValidateHttpsTrackers` to `false` to chase speed — they don't meaningfully affect throughput, only safety. + --- ## 🔧 Build Instructions From b47396e02906934cf4797277bd1da65acb5ed9da Mon Sep 17 00:00:00 2001 From: Andreas Tsaridas Date: Tue, 8 Sep 2026 12:05:10 +0000 Subject: [PATCH 5/6] feat(settings): make torrent settings configurable via env vars Every field on ServerSettings (BitTorrent connection/privacy/proxy options, cache size, proxy-streams, seeding) is now overridable with a STREAM_SERVER_* env var, read once when constructing the default settings on first startup (before any settings.json exists) -- after that, settings.json and the /settings API are the source of truth. Also retunes a few shipped defaults for a single-stream home connection in the ~100Mbps class: - bt_allow_multiple_connections_per_ip: false -> true (more usable connections behind CGNAT/shared-IP swarms) - bt_min_peers_for_stable: 5 -> 3 (starts playback sooner) - bt_download_speed_hard_limit: 0 (unlimited) -> ~11.25MB/s (~90% of 100Mbps), the one speed-limit field actually wired to libtorrent's download_rate_limit; leaving headroom avoids bufferbloat that would otherwise hurt both playback smoothness and other traffic on the link. Set to 0 via env/settings to go unlimited. - bt_max_connections stays at its existing default (800): per-torrent connections are separately clamped to 200 once the global max reaches 800 (see TorrentSpeedProfile::effective_connection_limits), so raising it further doesn't speed up a single stream -- it only matters when running several torrents concurrently. Verified on target: env vars applied correctly on a fresh volume, persisted settings.json still takes precedence once one exists. --- server/src/routes/system.rs | 154 +++++++++++++++++++++++++++++------- 1 file changed, 124 insertions(+), 30 deletions(-) diff --git a/server/src/routes/system.rs b/server/src/routes/system.rs index 4cea28ae..543c8aee 100644 --- a/server/src/routes/system.rs +++ b/server/src/routes/system.rs @@ -366,6 +366,46 @@ fn update_string_setting( } } +/// Reads `key` from the environment; falls back to `default` if unset or unparseable. +fn env_bool(key: &str, default: bool) -> bool { + match std::env::var(key) { + Ok(v) => match v.trim().to_ascii_lowercase().as_str() { + "1" | "true" | "yes" | "on" => true, + "0" | "false" | "no" | "off" => false, + _ => default, + }, + Err(_) => default, + } +} + +fn env_parsed(key: &str, default: T) -> T { + std::env::var(key) + .ok() + .and_then(|v| v.trim().parse().ok()) + .unwrap_or(default) +} + +fn env_string(key: &str, default: String) -> String { + std::env::var(key) + .ok() + .filter(|v| !v.is_empty()) + .unwrap_or(default) +} + +fn env_encryption_mode(key: &str, default: TorrentEncryptionMode) -> TorrentEncryptionMode { + std::env::var(key) + .ok() + .and_then(|v| parse_torrent_encryption_mode(&Value::String(v))) + .unwrap_or(default) +} + +fn env_proxy_type(key: &str, default: TorrentProxyType) -> TorrentProxyType { + std::env::var(key) + .ok() + .and_then(|v| parse_torrent_proxy_type(&Value::String(v))) + .unwrap_or(default) +} + impl Default for ServerSettings { fn default() -> Self { let cache_root = std::env::var("STREMIO_CACHE_ROOT") @@ -377,41 +417,95 @@ impl Default for ServerSettings { .to_string() }); + // Every field below is overridable via a STREAM_SERVER_* env var, read + // once here at first startup (before any settings.json exists). Once + // settings.json exists, it takes over and the /settings API is the + // way to change things -- these env vars only shape the *initial* + // defaults for a fresh deployment. Baseline values below are tuned + // for a single-stream home connection in the ~100Mbps class: + // bt_max_connections=800 already saturates the per-torrent cap + // (see enginefs::backend::TorrentSpeedProfile::effective_connection_limits, + // which clamps per-torrent connections to 200 once the global max + // reaches 800), so raising it further only helps if you're running + // several torrents at once -- it doesn't speed up a single stream. + // bt_download_speed_hard_limit defaults to ~90% of a 100Mbps line + // (11.25 MB/s) to leave headroom against bufferbloat/self-induced + // congestion, which otherwise costs more effective throughput (and + // playback smoothness) than it gains; set it to 0 to go unlimited. Self { app_path: std::env::current_exe() .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|_| "/usr/bin/stremio-server".to_string()), server_version: env!("CARGO_PKG_VERSION").to_string(), cache_root, - cache_size: 10.0 * 1024.0 * 1024.0 * 1024.0, // 10GB - proxy_streams_enabled: false, - bt_max_connections: enginefs::backend::DEFAULT_BT_MAX_CONNECTIONS, - bt_handshake_timeout: 20000, - bt_request_timeout: 10000, - bt_download_speed_soft_limit: 0.0, - bt_download_speed_hard_limit: 0.0, - bt_min_peers_for_stable: 5, - bt_enable_dht: default_bt_enable_dht(), - bt_enable_pex: default_bt_enable_pex(), - bt_enable_lsd: default_bt_enable_lsd(), - bt_encryption_mode: TorrentEncryptionMode::default(), - bt_anonymous_mode: default_bt_anonymous_mode(), - bt_allow_multiple_connections_per_ip: default_bt_allow_multiple_connections_per_ip(), - bt_listen_interfaces: default_bt_listen_interfaces(), - bt_outgoing_interfaces: String::new(), - bt_outgoing_port: 0, - bt_num_outgoing_ports: 0, - bt_proxy_type: TorrentProxyType::default(), - bt_proxy_host: String::new(), - bt_proxy_port: 0, - bt_proxy_username: String::new(), - bt_proxy_password: String::new(), - bt_proxy_hostnames: default_bt_proxy_hostnames(), - bt_proxy_peer_connections: default_bt_proxy_peer_connections(), - bt_proxy_tracker_connections: default_bt_proxy_tracker_connections(), - bt_proxy_send_host_in_connect: default_bt_proxy_send_host_in_connect(), - bt_validate_https_trackers: default_bt_validate_https_trackers(), - bt_ssrf_mitigation: default_bt_ssrf_mitigation(), + cache_size: env_parsed("STREAM_SERVER_CACHE_SIZE_BYTES", 10.0 * 1024.0 * 1024.0 * 1024.0), // 10GB + proxy_streams_enabled: env_bool("STREAM_SERVER_PROXY_STREAMS_ENABLED", false), + bt_max_connections: env_parsed( + "STREAM_SERVER_BT_MAX_CONNECTIONS", + enginefs::backend::DEFAULT_BT_MAX_CONNECTIONS, + ), + bt_handshake_timeout: env_parsed("STREAM_SERVER_BT_HANDSHAKE_TIMEOUT_MS", 20000), + bt_request_timeout: env_parsed("STREAM_SERVER_BT_REQUEST_TIMEOUT_MS", 10000), + bt_download_speed_soft_limit: env_parsed( + "STREAM_SERVER_BT_DOWNLOAD_SPEED_SOFT_LIMIT", + 0.0, + ), + bt_download_speed_hard_limit: env_parsed( + "STREAM_SERVER_BT_DOWNLOAD_SPEED_HARD_LIMIT", + 11.25 * 1024.0 * 1024.0, // ~90Mbps of a 100Mbps line, in bytes/sec + ), + bt_min_peers_for_stable: env_parsed("STREAM_SERVER_BT_MIN_PEERS_FOR_STABLE", 3), + bt_enable_dht: env_bool("STREAM_SERVER_BT_ENABLE_DHT", default_bt_enable_dht()), + bt_enable_pex: env_bool("STREAM_SERVER_BT_ENABLE_PEX", default_bt_enable_pex()), + bt_enable_lsd: env_bool("STREAM_SERVER_BT_ENABLE_LSD", default_bt_enable_lsd()), + bt_encryption_mode: env_encryption_mode( + "STREAM_SERVER_BT_ENCRYPTION_MODE", + TorrentEncryptionMode::default(), + ), + bt_anonymous_mode: env_bool( + "STREAM_SERVER_BT_ANONYMOUS_MODE", + default_bt_anonymous_mode(), + ), + bt_allow_multiple_connections_per_ip: env_bool( + "STREAM_SERVER_BT_ALLOW_MULTIPLE_CONNECTIONS_PER_IP", + true, + ), + bt_listen_interfaces: env_string( + "STREAM_SERVER_BT_LISTEN_INTERFACES", + default_bt_listen_interfaces(), + ), + bt_outgoing_interfaces: env_string("STREAM_SERVER_BT_OUTGOING_INTERFACES", String::new()), + bt_outgoing_port: env_parsed("STREAM_SERVER_BT_OUTGOING_PORT", 0), + bt_num_outgoing_ports: env_parsed("STREAM_SERVER_BT_NUM_OUTGOING_PORTS", 0), + bt_proxy_type: env_proxy_type("STREAM_SERVER_BT_PROXY_TYPE", TorrentProxyType::default()), + bt_proxy_host: env_string("STREAM_SERVER_BT_PROXY_HOST", String::new()), + bt_proxy_port: env_parsed("STREAM_SERVER_BT_PROXY_PORT", 0), + bt_proxy_username: env_string("STREAM_SERVER_BT_PROXY_USERNAME", String::new()), + bt_proxy_password: env_string("STREAM_SERVER_BT_PROXY_PASSWORD", String::new()), + bt_proxy_hostnames: env_bool( + "STREAM_SERVER_BT_PROXY_HOSTNAMES", + default_bt_proxy_hostnames(), + ), + bt_proxy_peer_connections: env_bool( + "STREAM_SERVER_BT_PROXY_PEER_CONNECTIONS", + default_bt_proxy_peer_connections(), + ), + bt_proxy_tracker_connections: env_bool( + "STREAM_SERVER_BT_PROXY_TRACKER_CONNECTIONS", + default_bt_proxy_tracker_connections(), + ), + bt_proxy_send_host_in_connect: env_bool( + "STREAM_SERVER_BT_PROXY_SEND_HOST_IN_CONNECT", + default_bt_proxy_send_host_in_connect(), + ), + bt_validate_https_trackers: env_bool( + "STREAM_SERVER_BT_VALIDATE_HTTPS_TRACKERS", + default_bt_validate_https_trackers(), + ), + bt_ssrf_mitigation: env_bool( + "STREAM_SERVER_BT_SSRF_MITIGATION", + default_bt_ssrf_mitigation(), + ), remote_https: None, transcode_profile: None, auto_update_enabled: default_auto_update_enabled(), @@ -420,7 +514,7 @@ impl Default for ServerSettings { cached_trackers: Vec::new(), trackers_last_updated: 0, trackers_source_url: default_trackers_url(), - seeding_enabled: default_seeding_enabled(), + seeding_enabled: env_bool("STREAM_SERVER_SEEDING_ENABLED", default_seeding_enabled()), } } } From f3c5f8e013afa0f113eca28f73dd4b093c7afb30 Mon Sep 17 00:00:00 2001 From: Andreas Tsaridas Date: Tue, 8 Sep 2026 12:06:00 +0000 Subject: [PATCH 6/6] docs: document STREAM_SERVER_* torrent settings env vars Table the concrete env vars and rationale for the 100Mbps-tuned defaults (bt_max_connections, bt_min_peers_for_stable, bt_allow_multiple_connections_per_ip, bt_download_speed_hard_limit), note that bt_download_speed_soft_limit is currently a no-op in the libtorrent backend, and cross-reference the --network host example. --- README.md | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5a86ebf6..c0fd12f1 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ docker run -d -it --name stream-server \ `--device /dev/dri:/dev/dri` passes through the host GPU for hardware-accelerated FFmpeg transcoding, if available. `-it` (kept even when running detached with `-d`) allocates a pty so `docker attach` can show the `--tui` dashboard later without disrupting the running server (see below). -This exact configuration has been built and deployed end-to-end (image build, container run, `/heartbeat`, `/settings` tuning, and TUI attach all verified) on an x86_64 Fedora/Docker host running alongside other, unrelated streaming services. +This exact configuration has been built and deployed end-to-end (image build, container run, `/heartbeat`, `/settings` tuning, and TUI attach all verified) on an x86_64 Fedora/Docker host running alongside other, unrelated streaming services. Add `STREAM_SERVER_BT_*` env vars from [Tuning for playback speed](#tuning-for-playback-speed) here too if you want them baked into the deployment — note that they only take effect on a *fresh* volume/config dir; `stream-server-data` above is reused on purpose to carry over an existing deployment's cache and settings, so its already-persisted `settings.json` takes precedence regardless of env vars. ## 🖥️ Accessing & Configuring the UI @@ -209,29 +209,31 @@ Key options: `btEnableDht`, `btEnablePex`, `btEnableLsd` (peer discovery), `btEn ### Tuning for playback speed -The defaults are already reasonable, but for a dedicated streaming box these knobs matter most: +Every field in `/settings` can also be set as a `STREAM_SERVER_*` environment variable (e.g. `btMaxConnections` → `STREAM_SERVER_BT_MAX_CONNECTIONS`, camelCase → SCREAMING_SNAKE_CASE), which is read once when the server constructs its **default** settings on first startup. This only shapes the *initial* config for a fresh deployment (no `settings.json` yet in the config dir) — once `settings.json` exists, it's the source of truth and `/settings` (below) is how you change things from then on. + +The shipped defaults are already tuned for a single-stream home connection in the ~100Mbps class: + +| Env var | Default | Why | +| --- | --- | --- | +| `STREAM_SERVER_BT_MAX_CONNECTIONS` | `800` | Per-torrent connections are separately clamped to 200 once the global max reaches 800 (`TorrentSpeedProfile::effective_connection_limits`), so raising this further doesn't speed up a single stream — it only helps if you're running several torrents at once. | +| `STREAM_SERVER_BT_MIN_PEERS_FOR_STABLE` | `3` (was `5`) | The engine reports "stable" (and playback can start) with fewer peers connected — a little less early-playback smoothness for a faster start. | +| `STREAM_SERVER_BT_ALLOW_MULTIPLE_CONNECTIONS_PER_IP` | `true` (was `false`) | More usable connections when many peers sit behind the same IP (CGNAT, seedboxes). | +| `STREAM_SERVER_BT_ENCRYPTION_MODE` | `allow` | Accepts both encrypted and plaintext peers, maximizing the usable peer pool. Only use `require` if your ISP throttles unencrypted BitTorrent traffic. | +| `STREAM_SERVER_BT_DOWNLOAD_SPEED_HARD_LIMIT` | `11796480` (~11.25MB/s, bytes/sec) | ~90% of a 100Mbps line. This is the one speed-limit field actually wired to libtorrent (`download_rate_limit`); leaving headroom below your real line rate avoids bufferbloat, which costs more effective throughput — and playback smoothness — than it gains. **Adjust this for your own link speed**: roughly `(Mbps × 1_000_000 / 8) × 0.9`. Set to `0` for unlimited. | +| `STREAM_SERVER_BT_DOWNLOAD_SPEED_SOFT_LIMIT` | `0` | Present in the API for parity, but not currently wired to anything in the libtorrent backend — changing it has no effect yet. | +| `STREAM_SERVER_SEEDING_ENABLED` | `true` | Keeps seeding after download completes, which improves download speed too via tit-for-tat reciprocity with peers. | + +All other `ServerSettings` fields (`btEnableDht`/`Pex`/`Lsd`, `btAnonymousMode`, `btListenInterfaces`, `btOutgoing*`, the `btProxy*` group, `btValidateHttpsTrackers`, `btSsrfMitigation`, `cacheSize`, `proxyStreamsEnabled`) follow the same `STREAM_SERVER_` pattern — see `server/src/routes/system.rs`'s `Default for ServerSettings` for the exact list. Don't set `btSsrfMitigation`/`btValidateHttpsTrackers` to `false` chasing speed — they don't meaningfully affect throughput, only safety. + +Equivalent one-off tuning via the running API instead of env vars (useful for a change you don't want to bake into the deployment): ```bash curl -X POST http://192.168.1.5:11470/settings \ -H "Content-Type: application/json" \ - -d '{ - "btMaxConnections": 1200, - "btMinPeersForStable": 3, - "btAllowMultipleConnectionsPerIp": true, - "btEncryptionMode": "allow", - "btDownloadSpeedSoftLimit": 0, - "btDownloadSpeedHardLimit": 0, - "seedingEnabled": true - }' + -d '{"btMaxConnections": 800, "btDownloadSpeedHardLimit": 11796480}' ``` -- `btMaxConnections` (up to `MAX_EFFECTIVE_BT_CONNECTIONS` = 1200, see `enginefs::backend::mod`): more simultaneous peer slots to source pieces from. -- `btMinPeersForStable`: lower means the engine reports "stable" (and playback can start) with fewer peers connected — trades a little early-playback smoothness for a faster start. -- `btEncryptionMode: "allow"` (not `"require"`/`"disable"`): accepts both encrypted and plaintext peers, maximizing the usable peer pool. Only `"require"` if your ISP throttles unencrypted BitTorrent traffic. -- `btDownloadSpeedSoftLimit`/`btDownloadSpeedHardLimit: 0`: unlimited. -- `seedingEnabled: true`: keeps seeding after download completes, which improves download speed too via tit-for-tat reciprocity with peers. -- Beyond `/settings`, run with `--network host` and publish/forward the `btListenInterfaces` port range — libtorrent's UPnP/NAT-PMP (always on) can then successfully port-map with your router for real incoming connections, which matters more for peer availability than any single setting above. -- Don't set `btSsrfMitigation`/`btValidateHttpsTrackers` to `false` to chase speed — they don't meaningfully affect throughput, only safety. +Beyond `/settings`, run with `--network host` and publish/forward the `btListenInterfaces` port range — libtorrent's UPnP/NAT-PMP (always on) can then successfully port-map with your router for real incoming connections, which matters more for peer availability than any single setting above. ---