docs: major overhaul and structural refactoring of Tuliprox documentation (#662)

Updated docs
This commit is contained in:
jojo141185
2026-03-24 15:20:01 +01:00
committed by GitHub
parent d9c30d3332
commit 2e1bb7204c
29 changed files with 2794 additions and 1954 deletions
+28 -10
View File
@@ -77,7 +77,6 @@ services:
image: ghcr.io/euzu/tuliprox-alpine:latest
working_dir: /app
volumes:
- /home/tuliprox/tuliprox:/app/tuliprox
- /home/tuliprox/config:/app/config
- /home/tuliprox/data:/app/data
- /home/tuliprox/cache:/app/cache
@@ -108,12 +107,31 @@ The detailed documentation lives in Markdown under `docs/` and is meant to be re
Main entry points:
- [`Getting Started`](docs/src/getting-started.md)
- [`Core Features`](docs/src/features.md)
- [`Config Reference`](docs/src/configuration/main-config.md)
- [`Sources And Targets`](docs/src/configuration/sources-and-targets.md)
- [`API Proxy`](docs/src/configuration/api-proxy.md)
- [`Streaming And Proxy Behavior`](docs/src/streaming-and-proxy.md)
- [`Mapping And Templates`](docs/src/mapping-and-templates.md)
- [`Deployment`](docs/src/deployment.md)
- [`Examples And Recipes`](docs/src/examples-and-recipes.md)
- **[Getting Started](docs/src/getting-started.md)**
- [Core Features](docs/src/features.md)
- [Build & Deploy](docs/src/build-and-deploy.md)
- **[Installation](docs/src/installation.md)**
- **[Configuration Overview](docs/src/configuration/overview.md)**
- [Main Config](docs/src/configuration/config.md)
- [Sources & Targets](docs/src/configuration/source.md)
- [API Proxy](docs/src/configuration/api-proxy.md)
- [Streaming & Proxy Behavior](docs/src/configuration/reverse-proxy.md)
- [Mapping & Templates](docs/src/configuration/template.md)
- [Examples & Recipes](docs/src/examples-recipes.md)
- [Operations & Debugging](docs/src/operations-debugging.md)
- [Troubleshooting & Resilience](docs/src/troubleshooting.md)
## Documentation strategy
The recommended format is:
- source in Markdown
- generated as static HTML
- shipped together with the frontend/web root
For this repository, `mdBook` is the best fit:
- Markdown stays easy to edit in Git
- static HTML output is simple to host
- it fits a Rust project better than a Node-heavy doc stack
- navigation and search come out of the box
+12 -2
View File
@@ -22,7 +22,9 @@ use crate::{
model::{AppConfig, Config, Healthcheck, HealthcheckConfig, ProcessTargets, SourcesConfig},
processing::processor::exec_processing,
repository::run_startup_migrations,
utils::{config_file_reader, db_viewer, init_logger, request::create_client, resolve_env_var},
utils::{
config_file_reader, db_viewer, init_bootstrap_logger, init_logger, request::create_client, resolve_env_var,
},
};
use arc_swap::{access::Access, ArcSwap};
use chrono::{DateTime, Utc};
@@ -143,6 +145,12 @@ const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP");
async fn main() {
let args = Args::parse();
// Initialize a minimal stdout logger immediately so that any error that
// occurs before `init_logger` (e.g. during path resolution) is visible.
// `init_logger` below will attempt a second `try_init` which silently
// fails; the format and module filters set here remain in effect.
init_bootstrap_logger(args.log_level.as_deref());
db_viewer(&args.db_viewer_args());
if args.genpwd {
@@ -310,7 +318,9 @@ fn get_file_paths(args: &Args) -> ConfigPaths {
let storage_path = if Path::new(&config_file).exists() {
match utils::read_config_file(&config_file, true, false) {
Ok(cfg) => resolve_storage_path(&home_path, cfg.storage_dir.as_deref()),
Err(err) => exit!("Can't read config file {} while resolving storage path: {err}", config_file),
Err(err) => {
exit!("Can't read config file {config_file} while resolving storage path: {err}")
}
}
} else {
resolve_storage_path(&home_path, None)
+1 -10
View File
@@ -1,7 +1,6 @@
use crate::api::model::{MetadataRetryDbKey, MetadataRetryDbValue};
use crate::repository::{BPlusTreeDiskIterator, BPlusTreeQuery, VirtualIdRecord};
use env_logger::{Builder, Target};
use log::{error, LevelFilter};
use log::error;
use serde::{Deserialize, Serialize};
use shared::model::{EpgChannel, M3uPlaylistItem, XtreamPlaylistItem};
use std::io::Write;
@@ -76,8 +75,6 @@ pub fn db_viewer(args: &DbViewerArgs<'_>) {
return;
}
init_db_viewer_logger();
let mut any_processed = false;
for request in requests {
if let Some(filename) = request.filename {
@@ -93,12 +90,6 @@ pub fn db_viewer(args: &DbViewerArgs<'_>) {
}
}
fn init_db_viewer_logger() {
let mut log_builder = Builder::from_default_env();
log_builder.target(Target::Stderr);
log_builder.filter_level(LevelFilter::Info);
let _ = log_builder.try_init();
}
fn try_dump_typed_db<K, V>(path: &Path) -> bool
where
+32 -11
View File
@@ -27,6 +27,37 @@ fn get_log_level(log_level: &str) -> LevelFilter {
}
}
fn apply_log_format(builder: &mut Builder) {
builder.format(|buf, record| {
let now = Local::now();
let timestamp = now.to_rfc3339_opts(SecondsFormat::Secs, now.offset().fix().local_minus_utc() == 0);
writeln!(buf, "[{timestamp} {} {}] {}", record.level(), record.target(), record.args())
});
}
/// Initializes a minimal stdout logger early in startup so that errors before
/// `init_logger` is called (e.g. during path resolution) are visible.
/// Reads log level from the CLI argument and the `TULIPROX_LOG` env var only —
/// config-file log level is not available yet at this point.
/// `init_logger` will attempt a second `try_init` which silently fails;
/// the format and module filters set here remain active.
pub fn init_bootstrap_logger(user_log_level: Option<&str>) {
let env_log_level = std::env::var("TULIPROX_LOG").ok();
let log_level = user_log_level
.map(std::string::ToString::to_string)
.or(env_log_level)
.unwrap_or_else(|| "info".to_string());
let mut log_builder = Builder::from_default_env();
log_builder.target(Target::Stdout);
apply_log_format(&mut log_builder);
log_builder.filter_level(get_log_level(&log_level));
for module in LOG_ERROR_LEVEL_MOD {
log_builder.filter_module(module, LevelFilter::Error);
}
let _ = log_builder.try_init();
}
pub fn init_logger(user_log_level: Option<&str>, config_file: &str) {
@@ -40,17 +71,7 @@ pub fn init_logger(user_log_level: Option<&str>, config_file: &str) {
let mut log_builder = Builder::from_default_env();
log_builder.target(Target::Stdout);
log_builder.format(move |buf, record| {
let now = Local::now();
let timestamp = now.to_rfc3339_opts(SecondsFormat::Secs, now.offset().fix().local_minus_utc() == 0);
writeln!(
buf,
"[{timestamp} {} {}] {}",
record.level(),
record.target(),
record.args()
)
});
apply_log_format(&mut log_builder);
// priority CLI-Argument, Env-Var, Config, Default
let log_level = user_log_level
+1 -1
View File
@@ -12,4 +12,4 @@ Generate password hashes with `tuliprox --genpwd`.
## RBAC groups
The optional `groups.txt` file defines permission groups in `group_name:permission1,permission2,...` format.
See the [Config Reference](docs/src/configuration/main-config.md) for the full permission list and file format.
See the [Config Reference](docs/src/configuration/config.md) for the full permission list and file format.
+16 -11
View File
@@ -1,13 +1,18 @@
# Summary
- [Introduction](index.md)
- [Getting Started](getting-started.md)
- [Core Features](features.md)
- [Configuration](configuration/overview.md)
- [Config Reference](configuration/main-config.md)
- [Sources And Targets](configuration/sources-and-targets.md)
- [API Proxy](configuration/api-proxy.md)
- [Streaming And Proxy](streaming-and-proxy.md)
- [Mapping And Templates](mapping-and-templates.md)
- [Deployment](deployment.md)
- [Examples And Recipes](examples-and-recipes.md)
- [Introduction & Architecture](./index.md)
- [Quick-Start](./getting-started.md)
- [Build & Deploy](./build-and-deploy.md)
- [Installation](./installation.md)
- [Configuration & Setup (Overview)](./configuration/overview.md)
- [config.yml (Core System)](./configuration/config.md)
- [Streaming & Proxy Behavior](./configuration/reverse-proxy.md)
- [Metadata Updater & FFprobe](./configuration/metadata-update.md)
- [Local Media Library](./configuration/local-library.md)
- [source.yml (Inputs, Panel API & Targets)](./configuration/source.md)
- [api-proxy.yml (Server, Users & RBAC)](./configuration/api-proxy.md)
- [template.yml (Macros & Regex)](./configuration/template.md)
- [mapping.yml (Mapper DSL & Logic)](./configuration/mapping-dsl.md)
- [Operations & Debugging (CLI & DB Dumps)](./operations-debugging.md)
- [Examples,Recipes & Ecosystem Stacks](./examples-recipes.md)
- [Troubleshooting & Resilience](./troubleshooting.md)
+235
View File
@@ -0,0 +1,235 @@
# 🛠️ Build & Deploy (For Professionals)
This guide covers advanced topics for developers and professionals who want to compile Tuliprox from source code, generate the documentation locally,
build custom Docker images, or deploy full ecosystem stacks.
## System Architecture
Tuliprox is built for extreme performance and modularity, consisting of three core components:
* **Rust Backend:** A high-performance, asynchronous engine handling stream brokering, metadata enrichment, and playlist processing.
* **Yew/WebAssembly Frontend:** A reactive management interface compiled to highly optimized WASM.
* **Static Assets:** Documentation and UI assets served directly from the configured web root.
The repository ships with various helper scripts located under `bin/` to simplify cross-platform toolchain setups:
* `bin/build_docs.sh`: Script to handle mdBook generation.
* `bin/build_fe.sh`: The shared entry point for building docs plus frontend assets.
* `bin/build_local.sh`: Compiles the backend locally.
* `bin/build_docker.sh`: Executes the multi-stage Docker build pipeline.
* `bin/release.sh`: Orchestrates a full production release build.
This project uses a `Makefile` to automate development tasks, tool installations, and build processes. This ensures a consistent
environment across different machines.
**Tool installations:**
* `make install-tools`: Full Setup: Installs Rustup, Cross, Trunk, wasm-bindgen, cargo-edit, mdBook, and markdownlint.
* `make rustup`: Installs the Rust toolchain and Cargo.
* `make cross`: Installs cross for multi-platform compilation.
* `make trunk`: Installs trunk for managing frontend builds.
* `make wasm-bindgen`: Installs the CLI tool for WebAssembly bindings.
* `make cargo-set-version`: Installs cargo-edit for version management.
* `make mdbook`: Installs mdBook for documentation generation.
* `make markdownlint`: Installs markdownlint-cli2 (requires Node.js/npm).
**Testing & Linting**:
* `make test`: Runs all workspace tests using the Stable toolchain.
* `make lint`: Runs clippy to find common mistakes and improve code quality.
* `make lint-fix`: Automatically applies clippy suggestions (where possible).
* `make markdown-lint`: Checks all .md files for formatting consistency.
**Formatting**:
We use specific Nightly rules to ensure the code style remains consistent across the project.
* `make fmt`: Formats all code in the workspace.
* `make fmt-check`: Verifies if the code is correctly formatted (used in CI).
---
## 1. Documentation Delivery & Generation
Tuliprox embeds its own documentation directly into the Web UI. The documentation workflow is completely Markdown-based to fit naturally into a Rust
project, avoiding the overhead of a full Node/React documentation stack.
**How it works:**
1. Write docs as Markdown in `docs/src`.
2. Generate static HTML with `mdBook` into `frontend/build/docs`.
3. Copy the generated site into `frontend/dist/static/docs` during the web build.
This gives you editable source files in Git without committing hand-written HTML.
### Main Build Commands (`make`)
Ensure you have `mdBook` installed (`cargo install mdbook`).
| Command | Purpose |
| :--- | :--- |
| `make docs` | Builds only the static documentation via mdBook. |
| `make docs-serve` | Serves the documentation locally on a dev port with live-reload. |
| `make web-dist` | Builds the frontend WASM app (via Trunk) and the docs together. Copies the docs into the frontend dist folder automatically. |
---
## 2. Building the Frontend
### WASM Optimization (`wasm-opt`)
Wasm optimization is handled by Trunk during the build (configured via `data-wasm-opt` in `index.html`). Trunk requires a compatible `wasm-opt`
binary in your system `PATH`.
**Recommended local setup script sequence:**
```bash
# Installs a specific version of wasm-tools locally in the repo
./bin/install_wasm_tools.sh 128
# Add it to the PATH
export PATH="$PWD/.tools/wasm-tools/version_128/bin:$PATH"
# Build the optimized release frontend
./bin/build_fe.sh release
```
---
## 3. Building from Source & Cross-Compilation
If you need to run Tuliprox on specific hardware (e.g., Raspberry Pi) or want a fully static Linux binary without `glibc` dependencies, follow these steps.
### Static Binary Builds (Linux MUSL)
For a portable Linux binary, the `musl` target is recommended.
**Prerequisite install on Debian/Ubuntu:**
```bash
rustup update
sudo apt-get install pkg-config musl-tools libssl-dev
rustup target add x86_64-unknown-linux-musl
```
**Build:**
```bash
cargo build -p tuliprox --release --target x86_64-unknown-linux-musl
```
### Cross-Compilation (ARM / Windows)
To compile for architectures other than your host, use the `cross` tool.
**For Raspberry Pi (ARMv7):**
```bash
cargo install cross
env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target armv7-unknown-linux-musleabihf
```
**For Windows (via Linux Cross-Compiler):**
```bash
rustup target add x86_64-pc-windows-gnu
sudo apt-get install gcc-mingw-w64
cargo build -p tuliprox --release --target x86_64-pc-windows-gnu
```
---
## 4. Custom Docker Builds (Multi-Arch)
Tuliprox utilizes an advanced Multi-Stage Docker build to compile the Rust backend, the Yew frontend, and extract static FFmpeg resources in a
single pipeline. This setup distinguishes between the **Docker Stage** (image flavor) and the **Rust Compilation Target** (CPU architecture).
### Full Pipeline Build
You can strictly target specific environments using the `--target` and `--build-arg` flags. These refer to the **destination system** where the
container will run, regardless of your local build machine's architecture:
* **`--target`**: Choose the image base. Use `scratch-final` for a hardened, minimal footprint or `alpine-final` if you need a shell for debugging.
* **`--build-arg RUST_TARGET`**: Defines the Instruction Set Architecture (ISA) for the binary.
```bash
# Build for x86_64 Linux (Standard Cloud/Desktop)
docker build --rm -f docker/Dockerfile -t tuliprox \
--target scratch-final \
--build-arg RUST_TARGET=x86_64-unknown-linux-musl .
# Build for ARM 64-bit (Apple Silicon / Raspberry Pi 4 & 5)
docker build --rm -f docker/Dockerfile -t tuliprox \
--target scratch-final \
--build-arg RUST_TARGET=aarch64-unknown-linux-musl .
# Build for ARMv7 (Raspberry Pi 3 / Older IoT)
docker build --rm -f docker/Dockerfile -t tuliprox \
--target scratch-final \
--build-arg RUST_TARGET=armv7-unknown-linux-musleabihf .
```
### Manual Docker Image
If you want to build the binary and web folder manually on your host and only package them into an image:
1. Compile the static binary (`bin/build_lin_static.sh`).
2. Compile the frontend (`yarn build`).
3. Change into the `docker` directory and copy the required files.
4. Run the manual Dockerfile build:
```bash
docker build -f Dockerfile-manual -t tuliprox .
```
*(Note: When running your custom local image via docker-compose, ensure you change `image: ghcr.io/euzu/tuliprox:latest` to `image: tuliprox` in
your `docker-compose.yml`, and set your timezone appropriately (`TZ=${TZ:-Europe/Paris}`).)*
---
## 5. Docker Container Templates — Deployment Guide
The Tuliprox repository contains ready-to-use Docker Compose templates for a secure reverse proxy stack with VPN egress and CrowdSec protection.
**Location:** `docker/container-templates/`
**Software baseline:** Traefik v3.5, a current Rust toolchain, and a current Docker/Compose setup.
### Legend & Port Overview
| Template | Folder | Purpose | Notable Ports (Internal) |
| :--- | :--- | :--- | :--- |
| **Traefik** | `traefik/` | Reverse proxy & TLS (ACME/DNS), dashboard, dynamic security middlewares, optional CrowdSec bouncer. | 80 `web`, 443 `websecure` |
| **Gluetun** | `gluetun/` | VPN egress via WireGuard; sidecars provide **SOCKS5**, **HTTP**, and **Shadowsocks** proxies bound to Gluetun’s network stack. | 1080/tcp (HTTP)<br>1388/tcp+udp (SOCKS5)<br>9388/tcp+udp (Shadowsocks) |
| **CrowdSec** | `crowdsec/` | LAPI + bouncers (Traefik & firewall) to protect services from brute-force and L7 attacks. | LAPI on `127.0.0.1:8080` (host) |
| **Tuliprox** | `tuliprox/` | Example application container with Traefik labels and `expose: 8901` for reverse proxying. | 8901 (internal) |
### Wiring up the Stack
1. **Networks:** Create external Docker networks first:
```bash
docker network create proxy-net
docker network create crowdsec-net
```
2. **Gluetun (VPN & Proxy Sidecar):**
Provide your Wireguard details in `gluetun-01/.env.wg-01` and set a user/pass in `.env.socks5-proxy`. Once started (`docker-compose up -d`), it
securely routes all traffic attached to its network through the VPN.
3. **Tuliprox Integration:**
In your Tuliprox `config.yml`, point the outgoing proxy to the SOCKS5 sidecar:
```yaml
proxy:
url: socks5://socks5-01:1388
username: "<socks5-proxy-user>"
password: "<socks5-proxy-password>"
```
Ensure Tuliprox is in the `proxy-net` network to reach the sidecar. All Provider-API, TMDB, and Stream-Proxy traffic will now strictly egress
through the WireGuard tunnel!
---
+127 -115
View File
@@ -1,185 +1,194 @@
# API Proxy
# 🛡️ Pillar 3: `api-proxy.yml` (Server, Users & RBAC)
`api-proxy.yml` tells Tuliprox which public server URLs to advertise and which users may access which targets.
The `api-proxy.yml` file acts as your Edge Gateway. It defines the public-facing URLs (virtual servers) that Tuliprox advertises
in its playlists, manages your end-users, and dictates which playlists (`targets`) those users can access, along with their
specific permissions, proxy modes, and priorities.
## Top-level entries
- `server`
- `user`
- `use_user_db`
- `auth_error_status`
```yaml
auth_error_status: 403
use_user_db: false
server:
user:
```
## `server`
| Parameter | Type | Impact |
| :--- | :--- | :--- |
| `auth_error_status` | Int (Default `403`) | The HTTP status code Tuliprox returns when a player sends invalid credentials or tokens. (Only applies to [Xtream/M3U API Endpoints](#api-endpoints-for-clients-players), stream paths, and resource paths, NOT the Web UI / REST API). |
| `use_user_db` | Bool (Default `false`) | If set to `true`, Tuliprox migrates all users from this YAML file into a highly performant SQLite database (`api_user.db`). **From then on, Tuliprox ignores the users in the YAML file!** You must subsequently manage users entirely via the Web UI Dashboard. Switching it back to `false` migrates them back to the YAML file. |
| `server` | List (Default `empty`) | See [Server Definitions](#1-server-definitions-server) for how to define servers |
| `user` | List (Default `empty`) | See [User Definitions](#2-user-definitions-user) for how to define users & permissions |
You can define multiple named servers.
Usually one is local and one is external.
One server should be named `default`.
---
## 1. Server Definitions (`server`)
Here you define multiple named "virtual servers". A server object describes the host structure that Tuliprox injects into the
stream URLs when generating M3U or Xtream playlists.
Typically, you define at least two: one for internal LAN access and one for external access via a reverse proxy (like Traefik or Nginx).
One server **must** strictly be named `default`.
```yaml
server:
- name: default
protocol: http
host: 192.168.1.9
port: "8901"
timezone: Europe/Paris
port: '8901'
timezone: Europe/Berlin
message: Welcome to tuliprox
- name: external
protocol: https
host: tv.example.com
port: "443"
timezone: Europe/Paris
message: Welcome to tuliprox
path: tuliprox
host: tv.my-domain.com
port: '443'
timezone: Europe/Berlin
path: iptv
```
Fields:
### Server Parameters
- `name`
- `protocol`
- `host`
- `port`
- `timezone`
- `message`
- `path`
| Parameter | Type | Technical Impact & Background |
| :--- | :--- | :--- |
| `name` | String | Internal reference ID (e.g., `external`). |
| `protocol` | String | `http` or `https`. *(Note: Tuliprox does not perform TLS termination itself; you need a proxy like Traefik/Nginx in front of it for HTTPS).* |
| `host` | String | The domain or IP address transmitted to the client. |
| `port` | String | The port your external proxy listens on (usually `443` for HTTPS). |
| `timezone` | String | Defines the timezone sent to the client via the Xtream API. |
| `message` | String | The welcome message displayed in IPTV players supporting the Xtream API. |
| `path` | String | **Background:** If you host Tuliprox not on a subdomain (`tv.dom.com`) but in a subdirectory (`dom.com/iptv`), specify `iptv` here. Tuliprox will automatically prefix all output URLs with this path. |
If Tuliprox is behind another reverse proxy, `path` simplifies URL rewriting.
---
## `user`
## 2. User Definitions (`user`)
Users are defined per target.
Each target can expose multiple credentials.
Users in Tuliprox are strictly bound to a specific `target` (defined in `source.yml`). A single target can have multiple user
credentials attached to it.
```yaml
user:
- target: xc_m3u
- target: my_livingroom_target
credentials:
- username: demo
password: secret1
token: token1
- username: john
password: mysecurepassword
token: auth_token_abc
proxy: reverse
server: default
exp_date: 1672705545
max_connections: 1
epg_timeshift: Europe/Paris
status: Active
priority: 0
user_ui_enabled: true
priority: -10
```
Credential fields:
**Crucial Concept:** By default, Tuliprox acts purely as a stream mapper. If you want Tuliprox to actively evaluate the `status`,
enforce the `exp_date`, or kick users who breach their `max_connections`, you **must** set `user_access_control: true` globally
in your `config.yml`. Without it, these fields are purely cosmetic!
- `username`
- `password`
- `token`
- `proxy`
- `server`
- `epg_timeshift`
- `max_connections`
- `status`
- `exp_date`
- `priority`
- `user_ui_enabled`
- `user_access_control`
## Credential Parameters (Deep-Dive)
`username` and `password` are mandatory.
`token` is optional and must be unique if set.
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `username` / `password` | String | | **Mandatory.** The standard Xtream-Codes / M3U credentials used for authentication. |
| `token` | String | | Optional. Allows login via a URL parameter (`?token=XYZ`) instead of user/pass. Must be globally unique if set. |
| `proxy` | String | `reverse` | Defines the proxy mode for this user (see [proxy modes](#proxy-modes-proxy) below). |
| `server` | String | `default` | Which server block (host/port) is rendered into the playlist for this user. |
| `epg_timeshift` | String | | Shifts EPG times for users in different time zones. Formats supported: hour offsets (e.g., `-2:30`, `1:45`, `+0:15`, `2`) or exact timezones (e.g., `Europe/Paris`). Only applies when `epg_url` is configured in the source. |
| `max_connections` | Int | `0` | Hard limit of concurrent streams for *this* user. `0` = Unlimited. **Requires** `user_access_control: true` in `config.yml` to be enforced. |
| `status` | Enum | `Active` | Possible values: `Active`, `Trial`, `Expired`, `Banned`, `Disabled`, `Pending`. **Requires** `user_access_control: true` in `config.yml` to block non-active streaming. |
| `exp_date` | UnixTs | | Locks the user out after this Unix timestamp. **Requires** `user_access_control: true` in `config.yml` to be enforced. |
| `user_ui_enabled` | Bool | `true` | Allows this specific user to log into the Web UI to manage their own favorites/bouquets. |
| `priority` | Int (i8) | `0` | Stream preemption priority. Lower numbers equal higher priority. Negative numbers allowed. (see [user priority](#user-priorities-priority) below) |
## Proxy mode
---
`proxy` can be:
### Proxy Modes (`proxy`)
- `redirect`
- `reverse`
- `reverse[live]`
- `reverse[live,vod]`
This is the most crucial field governing traffic flow for the user. When to use which?
Meaning:
* **`redirect`**: Tuliprox responds to the client with an HTTP 302 Redirect, pointing directly to the upstream provider's URL
(or rotating through DNS failover IPs).
* *When to use:* To save massive bandwidth on your server (Tuliprox only acts as a matchmaker).
* *Tradeoff:* **No** connection limits, buffering, bandwidth throttling, or custom fallback videos are applied!
* **`reverse`**: Tuliprox downloads the video stream from the provider onto your server and pipes it to the client.
* *When to use:* This is required for connection limits, fallback videos, caching, bandwidth throttling, and shared streams to function.
* **Partial Syntax**: You can mix and match! `reverse[live]` forces Live-TV through Tuliprox (allowing shared streams) but redirects
VODs (saving bandwidth). `reverse[live,vod]` routes everything except Series episodes through Tuliprox.
- `redirect`: Tuliprox returns provider URLs
- `reverse`: Tuliprox proxies stream traffic itself
- subset syntax: reverse only for selected content types
### User Priorities (`priority`)
## Priority
**Architecture Detail:** Tuliprox utilizes a *Unix Nice-Scale* (value range `-128` to `127`). A **lower** number means a **higher**
priority. The default is `0`.
User priority is optional and defaults to `0`.
**Practical Use Cases:**
Rules:
1. **Admin Override:** Set your personal user to `-10`. Set your friends to `0`. If provider limits are exhausted, you will
forcefully kick a friend to watch TV.
2. **Family vs Guests:** Set your TV to `0`, kids to `10`, and guests to `20`. Guests get kicked first.
- lower number = higher priority
- negative values are allowed
- higher-priority users can preempt lower-priority traffic when provider capacity is exhausted
- equal priority does not preempt a different running stream
- `max_connections` is independent of priority
**The Preemption Scenario:**
Your upstream provider allows 2 concurrent connections. User A (Priority `0`) is watching TV. User B (Priority `0`) is watching
a VOD. The provider limit is exhausted.
Now you, the Admin (User C with Priority `-10`), want to watch.
Probe tasks use the same style of priority scale via `metadata_update.probe.user_priority`.
1. Because your priority is *higher* (lower number), Tuliprox scans for active connections with the *lowest* priority on that
specific provider.
2. Since A and B are tied (both `0`), Tuliprox targets the stream that has been running the longest (tie-breaker based on stream age).
3. Tuliprox forcefully terminates User A's provider connection, serves User A a fallback video (`low_priority_preempted.ts`), and
instantly claims the freed provider slot for you.
## Access control
*(Note: Internal FFprobe metadata probe tasks run by default at the absolute lowest priority (`127`) and are immediately
preempted/killed if any real user needs the slot.)*
When `user_access_control` is enabled in `config.yml`, Tuliprox also evaluates:
---
- `status`
- `exp_date`
- `max_connections`
&nbsp;
for each user.
## Additional Information
## `use_user_db`
## API Endpoints for Clients (Players)
If `use_user_db: true` is enabled, users are stored in the user database instead of the YAML file.
The Web UI should then be used to add, edit or remove users.
After configuring the api-proxy, you can use these endpoints in players like TiviMate, IPTV Smarters, or VLC.
Tuliprox migrates users automatically when switching between YAML and DB mode.
*(Replace `<host>:<port>` with your Server definition).*
## `auth_error_status`
HTTP status code returned when authentication fails (invalid or missing credentials).
Defaults to `403` (Forbidden).
```yaml
auth_error_status: 403
```
This setting applies to the streaming and playlist API endpoints
(`player_api.php`, `get.php`, `xmltv.php`, stream paths, resource paths).
It does **not** affect the Web UI / REST API (`/api/v1/…`) or HDHomeRun endpoints,
which always use their own fixed status codes.
## Access URLs
Common access patterns:
Xtream:
**Xtream Codes API:**
```text
http://host:port/player_api.php?username=USER&password=PASS
http://host:port/player_api.php?token=TOKEN
http://<host>:<port>/player_api.php?username=<USER>&password=<PASS>
http://<host>:<port>/player_api.php?token=<TOKEN>
```
M3U:
**M3U Playlist URL:**
```text
http://host:port/get.php?username=USER&password=PASS
http://host:port/get.php?token=TOKEN
http://<host>:<port>/get.php?username=<USER>&password=<PASS>
http://<host>:<port>/get.php?token=<TOKEN>
```
XMLTV:
**XMLTV EPG URL:**
```text
http://host:port/xmltv.php?username=USER&password=PASS
http://<host>:<port>/xmltv.php?username=<USER>&password=<PASS>
```
REST-friendly aliases also work:
Tuliprox also offers **REST-friendly aliases** in case restrictive firewalls or ISP blocks target `.php` extensions:
- `m3u` instead of `get.php`
- `xtream` instead of `player_api.php`
- `epg` instead of `xmltv.php`
* `/xtream` instead of `player_api.php`
* `/m3u` instead of `get.php`
* `/epg` instead of `xmltv.php`
## Reverse proxy in front of Tuliprox
## Reverse Proxy in front of Tuliprox
If another proxy sits in front of Tuliprox, make sure it forwards:
If another proxy sits in front of Tuliprox (like Nginx or Traefik), you must ensure it forwards the correct headers so
Tuliprox's IP-based rate limiting and connection kicking works.
- `X-Real-IP`
- `X-Forwarded-For`
Make sure it forwards:
Example nginx block:
* `X-Real-IP`
* `X-Forwarded-For`
Example Nginx block:
```nginx
location /tuliprox {
@@ -187,6 +196,8 @@ location /tuliprox {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.1.9:8901/;
# ABSOLUTELY CRITICAL FOR VIDEO STREAMS:
proxy_redirect off;
proxy_buffering off;
proxy_request_buffering off;
@@ -201,6 +212,7 @@ Example Traefik labels:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.tuliprox.rule=Host(`tv.my-domain.io`) && (PathPrefix(`/tv`) || PathPrefix(`/tuliprox`))"
- "traefik.http.routers.tuliprox.rule=Host(`tv.example.com`) && (PathPrefix(`/tv`) || PathPrefix(`/tuliprox`))"
- "traefik.http.middlewares.tuliprox-strip.stripprefix.prefixes=/tv"
- "traefik.http.routers.tuliprox.middlewares=tuliprox-strip@docker,forward-real-ip@file"
```
+384
View File
@@ -0,0 +1,384 @@
# 🏛️ config.yml (Core System)
The `config.yml` is the primary configuration file of Tuliprox. It dictates the engine's core runtime behavior, memory management,
caching mechanisms, background schedulers, and external integrations (like HDHomeRun, GeoIP, and the Web UI).
## Top-level entries
```yaml
process_parallel: false
disk_based_processing: false
storage_dir: ./data
default_user_agent: Tuliprox/...
backup_dir: ./data/backup
user_config_dir: ./data/user
mapping_path: mapping.yml
template_path: template.yml
update_on_boot: false
config_hot_reload: false
accept_insecure_ssl_certificates: false
sleep_timer_mins: null
connect_timeout_secs: 10
user_access_control: false
custom_stream_response_path: null
custom_stream_response_timeout_secs: 0
api:
web_ui:
log:
schedules:
messaging:
video:
proxy:
ipcheck:
hdhomerun:
library:
reverse_proxy:
metadata_update:
```
### 1. Global System & Storage Settings (Flat Keys)
| Parameter | Type | Required | Default | Technical Impact & Background |
| :--- | :--- | :---: | :--- | :--- |
| `process_parallel` | Bool | No | `false` | Activates multi-threading during playlist processing. **Background:** If you have 5 providers, Tuliprox processes them sequentially by default. Setting this to `true` processes all 5 simultaneously using multiple CPU cores. *Warning:* This establishes parallel downloads to your providers. Verify this does not violate your provider's connection limits! |
| `disk_based_processing` | Bool | No | `false` | **Tradeoff Guidance:** Normally, Tuliprox loads playlists into RAM. With `true`, every chunk is manipulated directly on disk (using a B+Tree database). **Use this on low-end hardware (e.g., Raspberry Pi) or with massive playlists (>500k streams)** to prevent Out-Of-Memory crashes. It significantly increases Disk I/O load, so it is slower but much safer for tight memory footprints. |
| `storage_dir` | String | No | `./data` | Root directory for all runtime data (B+Tree databases, downloads, caches). Relative paths are resolved against the Tuliprox Home Directory. |
| `default_user_agent` | String | No | `Tuliprox/...` | Fallback HTTP `User-Agent` used for upstream provider requests if the input definition or client request does not explicitly provide one. |
| `backup_dir` | String | No | `{storage_dir}/backup` | Storage location for config backups (e.g., triggered via "Save Configuration" in the Web UI). |
| `user_config_dir` | String | No | `{storage_dir}/user` | Storage location for user-specific configurations (like favorites or custom bouquets created via the Web UI). |
| `mapping_path` | String | No | `mapping.yml` | Path to the mapping file. **Pro-Tip:** If you specify a folder path here (e.g., `./config/mappings/`), Tuliprox loads *all* `.yml` files in that folder in alphanumeric order! Ideal for structuring complex setups. |
| `template_path` | String | No | `template.yml` | Path to the template macro file. Specifying a folder here is also possible and highly recommended. |
| `update_on_boot` | Bool | No | `false` | Forces Tuliprox to immediately query all providers and rebuild all playlists upon startup. If `false`, the proxy serves the local DB cache from the last run until the scheduler triggers the next update. |
| `config_hot_reload` | Bool | No | `false` | **Background:** Spawns a filesystem watcher for `mapping.yml` and `api-proxy.yml`. Upon saving, mappings and user credentials become active immediately *without* requiring a server restart. |
| `accept_insecure_ssl_certificates` | Bool | No | `false` | Set to `true` if your upstream provider uses expired, self-signed, or improperly configured HTTPS certificates. Otherwise, the HTTP client drops the connection securely. |
| `sleep_timer_mins` | Int | No | `null` | Automatic kill-switch for proxied streams. Forcibly terminates active stream connections after X minutes (Protects against users falling asleep with the TV on). |
| `connect_timeout_secs` | Int | No | `10` | Maximum time (in seconds) Tuliprox waits to establish the initial TCP connection to a provider. `0` disables the timeout (Warning: risk of hanging threads!). |
| `user_access_control` | Bool | No | `false` | **Security:** If `true`, Tuliprox actively enforces `status` (Active/Banned), `exp_date`, and `max_connections` constraints for users defined in `api-proxy.yml`. If false, those fields are ignored. |
| `custom_stream_response_path` | String | No | `null` | Directory path where Tuliprox looks for custom fallback `.ts` files (e.g., `user_connections_exhausted.ts`, `channel_unavailable.ts`). See section [Custom Stream Response](#custom-stream-responses-fallback-videos) for more details. |
| `custom_stream_response_timeout_secs` | Int | No | `0` | Hard timeout (in seconds) that forces the fallback video stream to terminate to prevent infinite bandwidth usage. `0` means endless loop. |
### 2. Subsections (Object Keys)
| Block | Description | Link |
| :--- | :--- | :--- |
| `api` | Internal web server binding settings. | [See section](#3-api-server-api) |
| `web_ui` | Web Dashboard, RBAC, and Authentication. | [See section](#4-web-ui--administration-web_ui) |
| `log` | Console output verbosity and sanitization. | [See section](#5-logging-log) |
| `schedules` | Automated background tasks (Cronjobs). | [See section](#6-schedules-schedules) |
| `messaging` | Webhooks & Push-Notifications (Telegram, Discord, etc.). | [See section](#7-messaging-messaging) |
| `video` | Extension mapping and Web UI download behavior. | [See section](#8-video--web-search-video) |
| `proxy` | SOCKS5/HTTP proxy settings for outgoing requests. | [See section](#9-outgoing-proxy-proxy) |
| `ipcheck` | IP detection to verify in the Web UI which public IP Tuliprox is currently using. | [See section](#10-ip-check-ipcheck) |
| `hdhomerun` | Virtual DVB-C/T network tuner emulation. | [See section](#11-hdhomerun-emulation-hdhomerun) |
| `library` | Local Media Library integration. | [See Local Library](./local-library.md) |
| `reverse_proxy` | Streaming buffers, rate limits, caching. | [See Reverse Proxy](./reverse-proxy.md) |
| `metadata_update` | TMDB matching, FFprobe processing, Job Queues. | [See Metadata Update](./metadata-update.md) |
*(Note: The advanced topics **Local Library**, **Reverse Proxy** and **Metadata Update** are extremely extensive and have their own
dedicated subchapters. Here we cover the global base settings.)*
---
## 3. API Server (`api`)
Controls the internal web server of Tuliprox. This does *not* dictate the public URLs given to clients (those belong in
`api-proxy.yml`), but rather the physical socket binding on your host machine.
```yaml
api:
host: 0.0.0.0
port: 8901
web_root: ./web
```
| Parameter | Type | Required | Default | Technical Impact & Background |
| :--- | :--- | :---: | :--- | :--- |
| `host` | String | No | `0.0.0.0` | Bind interface. `0.0.0.0` listens on all network cards. `127.0.0.1` restricts access to localhost (useful if you force traffic through a local Nginx/Traefik reverse proxy). |
| `port` | Int | No | `8901` | The listening port for proxy streams, the Web UI, and all REST APIs. |
| `web_root` | String | No | `./web` | Physical path to the compiled Wasm/JS/CSS frontend assets of the Web UI. |
---
## 4. Web UI & Administration (`web_ui`)
Tuliprox ships with a comprehensive Web Dashboard containing a Web Player, Playlist Editor, User Management, and Live Logs.
```yaml
web_ui:
enabled: true
user_ui_enabled: true
path: admin
player_server: default
kick_secs: 90
combine_views_stats_streams: false
auth:
enabled: true
issuer: tuliprox
secret: "YOUR_SECRET_JWT_KEY_HERE"
token_ttl_mins: 30
userfile: user.txt
groupfile: groups.txt
```
### Web UI Parameters
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `enabled` | Bool | `true` | Completely toggles the Web Dashboard and its REST API endpoints on or off. |
| `user_ui_enabled` | Bool | `true` | Allows standard proxy users (not just admins) to log into the Web UI to manage their own favorite bouquets. |
| `path` | String | `""` | Base path for the UI (e.g., `admin`). Critical for reverse proxy subfolder setups so assets load from `example.com/admin/assets/`. |
| `player_server` | String | `default` | Determines which virtual server block from `api-proxy.yml` is used to construct the streaming URLs when playing a channel directly within the Web UI player. |
| `kick_secs` | Int | `90` | **Background:** When you kick a user via the Dashboard, they are not only disconnected but hard-blocked at the IP/User level for X seconds. This prevents their IPTV player's auto-reconnect logic from instantly stealing the provider slot back. |
| `combine_views_stats_streams` | Bool | `false` | Combines the "Server Stats" and "Active Streams" views into a single unified window in the UI. |
### Authentication & RBAC (`web_ui.auth`)
Tuliprox features a robust Role-Based Access Control (RBAC) system.
| Parameter | Type | Required | Default | Technical Impact & Background |
| :--- | :--- | :---: | :--- | :--- |
| `secret` | String | Yes | `(Random)` | Critical for JWT cookie encryption. Generate a random 32-char hex string (e.g., `openssl rand -hex 16`). If omitted, Tuliprox generates one in-memory, but all active logins will invalidate on every server restart! |
| `token_ttl_mins` | Int | No | `30` | How long a login session remains valid. Setting this to `0` makes the token effectively valid for 100 years (Extreme Security Risk!). |
| `userfile` | String | No | `user.txt` | The file storing Admins and Web Users. |
| `groupfile` | String | No | `groups.txt` | The RBAC (Role-Based Access Control) definition file. |
#### Structure of `user.txt`
This file stores users, Argon2 password hashes, and RBAC groups. Generate secure passwords via CLI: `./tuliprox --genpwd`.
Format: `username:argon2_hash[:group1,group2]`
```text
# A normal Admin (No group specified = Fallback to built-in Admin role)
admin:$argon2id$v=19$m=19456,t=2,p=1$QUp...
# An Editor assigned to specific permission groups
editor:$argon2id$v=19$m=19456,t=2,p=1$Y2F...:playlist_manager,user_manager
```
#### Structure of `groups.txt`
Define group permissions here. An editor might be allowed to update playlists (`playlist.write`) but forbidden from viewing or
changing `config.yml` (`config.read`).
Format: `group_name:permission1,permission2,...`
```text
viewer:config.read,source.read,playlist.read,system.read,library.read
playlist_manager:playlist.read,playlist.write,source.read
```
Available Permissions: `config.read/write`, `source.read/write`, `user.read/write`, `playlist.read/write`, `library.read/write`,
`system.read/write`, `epg.read/write`. Note: Write does not imply Read. A group must explicitly grant both if users need to view
and edit content.
---
## 5. Logging (`log`)
Controls console output verbosity and sanitization.
```yaml
log:
sanitize_sensitive_info: true
log_active_user: false
log_level: info
```
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `log_level` | String | `info` | Verbosity. Possible values: `trace`, `debug`, `info`, `warn`, `error`. Can be overridden per-module (e.g., `tuliprox=debug,hyper_util=warn`). |
| `sanitize_sensitive_info` | Bool | `true` | **Critical:** Masks passwords, provider URLs, and external client IPs in the logs with `***`. Highly recommended to keep `true` so you can safely share logs on GitHub/Discord for support without leaking credentials. |
| `log_active_user` | Bool | `false` | Periodically writes the current active client connection count as an INFO message to the log file. |
---
## 6. Schedules (`schedules`)
Automate your background updates here.
*Important:* Tuliprox uses standard cron syntax, **but strictly includes seconds as the first field**
(7 fields in total: `Sec Min Hour Day Month Day-of-Week Year`)!
```yaml
schedules:
# Every morning at 08:00:00 (Seconds = 0, Minutes = 0, Hours = 8)
- schedule: "0 0 8 * * * *"
type: PlaylistUpdate
targets: [ "m3u_target", "xtream_target" ] # Optional: Only update specific targets
# Every evening at 20:00:00
- schedule: "0 0 20 * * * *"
type: LibraryScan
# Every Monday at 04:00:00
- schedule: "0 0 4 * * 1 *"
type: GeoIpUpdate
```
| Parameter | Type | Description |
| :--- | :--- | :--- |
| `schedule` | String | Cron expression with 7 fields (Seconds included at the start). |
| `type` | Enum | The task to execute. Valid values: `PlaylistUpdate`, `LibraryScan`, `GeoIpUpdate`. |
| `targets` | List | *(Optional, only for PlaylistUpdate)* List of target names to restrict the update to. If omitted, all enabled targets are updated. |
---
## 7. Messaging (`messaging`)
Tuliprox can proactively notify you via Push-Notifications when updates fail, finish, or when specific channels are added/removed
from a watched group. **Why is this useful?** Because it allows you to instantly detect upstream provider issues or simply let you
know when new movies are added to your playlist.
*(Note: You must explicitly opt-in via `notify_on` to receive messages!)*
```yaml
messaging:
notify_on:[ "info", "stats", "error", "watch" ]
telegram:
markdown: true
bot_token: "<TOKEN>"
chat_ids:
- "<CHAT_ID>"
- "<CHAT_ID>:<MESSAGE_THREAD_ID>" # For group topics
templates:
stats: 'file:///config/messaging_templates/telegram_stats.templ'
discord:
url: "<WEBHOOK_URL>"
pushover:
token: "<API_TOKEN>"
user: "<USER_KEY>"
rest:
url: "https://my-api.local/alert"
method: "POST"
headers:
- "Content-Type: application/json"
```
**Template Auto-File Behavior & Variables (Handlebars):**
You can reference custom formatting templates (`file://` or HTTP URLs). If the file doesn't exist, Tuliprox can automatically
populate the `messaging_templates/` folder with default templates if missing. Variables injected during runtime:
* `{{message}}`: The raw text.
* `{{kind}}`: The event type (`info`, `error`, `stats`, `watch`).
* `{{timestamp}}`: RFC3339 timestamp.
* `{{stats}}`: Array of metrics per Source/Input (`raw.groups`, `processed.channels`, `took`).
* `{{processing.errors}}`: A combined string of all errors encountered during the update run.
* `{{watch}}`: Contains data about added/removed items triggered by the `watch` event in your targets.
---
## 8. Video & Web Search (`video`)
Optional video-related behaviors, mostly utilized by the Web UI.
```yaml
video:
web_search: "https://www.imdb.com/search/title/?title={}"
extensions: ["mkv", "mp4", "avi", "ts", "webm"]
download:
directory: /tmp/tuliprox_downloads
organize_into_directories: true
episode_pattern: '.*(?P<episode>[Ss]\d{1,2}(.*?)[Ee]\d{1,2}).*'
```
* `extensions`: Defines which file endings Tuliprox categorizes as VOD/Video content when transforming M3U to Xtream.
* `web_search`: A template URL used in the Web UI to quickly search for a movie title (replaces `{}` with the title).
* `download`: Configuration for the Web UI's "Download Video" button.
* `directory`: Where the downloaded files are saved.
* `organize_into_directories`: If true, Tuliprox automatically creates neat subfolders for series.
* `episode_pattern`: Crucial for the directory organization. It uses the mandatory Named Capture Group `(?P<episode>...)` in the
Regex to identify and strip the episode identifier (e.g., `S01E01`) from the filename, ensuring all episodes of a show land in
the exact same base-show folder.
---
## 9. Outgoing Proxy (`proxy`)
If Tuliprox itself must operate behind a corporate proxy or VPN (e.g., a Gluetun WireGuard container):
```yaml
proxy:
url: socks5://192.168.1.6:8123
username: "opt_user"
password: "opt_password"
```
Setting this forces **every** outgoing request (Playlist downloads, TMDB API calls, FFprobe stream analysis, and Reverse-Proxy
Video Streaming) through this proxy.
---
## 10. IP-Check (`ipcheck`)
To verify in the Web UI which public IP Tuliprox is currently using (crucial when verifying VPN routing or diagnosing geo-blocks),
Tuliprox queries a detection API:
```yaml
ipcheck:
url_ipv4: https://ipinfo.io/ip
```
You can also define `url_ipv6`, or generic `url` alongside `pattern_ipv4` or `pattern_ipv6` regexes to extract the IP from
complex JSON responses.
---
## 11. HDHomeRun Emulation (`hdhomerun`)
**Deep-Dive Feature:** Tuliprox can masquerade on the local network as a physical "SiliconDust HDHomeRun" DVB-C/T network tuner.
Media servers like **Plex**, **Jellyfin**, **Emby**, or **TVHeadend** will automatically discover Tuliprox via UPnP as a real
hardware antenna and ingest Live-TV natively into their Live-DVR systems.
Tuliprox utilizes standardized UPnP/SSDP (UDP Port 1900) and the proprietary SiliconDust UDP discovery protocol (UDP Port 65001).
```yaml
hdhomerun:
enabled: true
auth: false # If true, lineup.json requires Basic Auth (using the assigned user's credentials)
devices:
- name: hdhr1 # MUST match the 'device' name in the output target of source.yml!
tuner_count: 4 # Tells Plex that 4 channels can be watched simultaneously
port: 5004 # Unique TCP Port for this specific virtual antenna API
device_id: "107ABCDF" # 8-char hex code (Port 65001). If left blank, Tuliprox generates a valid one with correct checksums.
device_udn: "uuid:..." # Unique Device Name (Port 1900). Recommended to leave blank for auto-generation.
friendly_name: "Tuliprox Living Room"
```
Both `device_id` and `device_udn` are necessary for Tuliprox to satisfy both the official SiliconDust apps and third-party UPnP
scanners simultaneously.
---
&nbsp;
## Additional Information
## Custom Stream Responses (Fallback Videos)
When a stream fails to load at the provider (HTTP 404/502) or a user reaches their connection limit, Tuliprox can seamlessly
substitute a fallback info-video (as a `.ts` stream) instead of brutally closing the TCP connection. Dropping connections often
causes hardware players or Smart TVs to freeze.
```yaml
custom_stream_response_path: /home/tuliprox/resources
custom_stream_response_timeout_secs: 20
```
| Name | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `custom_stream_response_path` | String | `(Empty)` | Directory path where Tuliprox looks for exactly named `.ts` files. |
| `custom_stream_response_timeout_secs` | Int | `0` | Hard timeout (in seconds) that forces the fallback video stream to terminate to prevent infinite bandwidth usage. `0` means the fallback loops endlessly until the user switches channels. |
**Filenames searched for in the directory:**
* `channel_unavailable.ts` (Provider returns 404/502/Timeout)
* `user_connections_exhausted.ts` (User hit their `max_connections` limit)
* `provider_connections_exhausted.ts` (Provider has no free slots left)
* `low_priority_preempted.ts` (User was kicked by an Admin with higher priority)
* `user_account_expired.ts` (User's `exp_date` reached)
* `panel_api_provisioning.ts` (Loops while a new Provider Account is generated via Panel API)
**How it works:**
Tuliprox searches the specified folder for exactly named `.ts` files. If found, they are looped back to the client. The
`custom_stream_response_timeout_secs` parameter hard-kills the fallback stream after X seconds to prevent infinite bandwidth usage.
---
+138
View File
@@ -0,0 +1,138 @@
# 📂 Local Media Library
Tuliprox is not limited to external IPTV providers. Through the **Library** module, it can recursively scan, catalog, and
seamlessly integrate local movie and TV show collections (much like Plex or Jellyfin do) directly into the Xtream/M3U outputs
for your IPTV clients.
## Core Features
* **Recursive Scanning:** Traverses directories looking for supported video formats (`.mkv`, `.mp4`, etc.).
* **Auto-Classification:** Automatically detects whether a file is a Movie or a Series episode (e.g., `Breaking.Bad.S01E01.mkv`)
using the internal PTT (Parse Torrent Title) engine.
* **Multi-Source Metadata:** Reads Kodi/Jellyfin/Emby compatible `.nfo` files. If no `.nfo` is present, it automatically queries
the TMDB API for covers, plots, cast, and trailers.
* **Incremental Scans:** Uses file modification timestamps to only scan and process new or altered files, ensuring extremely fast
updates.
* **Stable Virtual IDs:** Generates stable, deterministic UUIDs for local files, ensuring that channel/stream IDs in your IPTV
client remain constant across updates.
---
## Configuration (`config.yml`)
You enable the library globally in the `library` block of your `config.yml`. The metadata caches and resolved TMDB data are
physically stored inside `metadata_update.cache_path/library` (relative to your `storage_dir`).
```yaml
library:
enabled: true
scan_directories:
- enabled: true
path: "/media/movies"
content_type: movie # Forces everything in this folder to be treated as a movie
recursive: true
- enabled: true
path: "/media/shows"
content_type: auto # Uses PTT to guess S01E01 structures
recursive: true
supported_extensions:
- "mp4"
- "mkv"
- "avi"
- "ts"
metadata:
fallback_to_filename: true # Uses parsed filename details if TMDB/NFO fails
read_existing:
kodi: true
plex: false
jellyfin: false
formats:
- "nfo"
playlist:
movie_category: "Local Movies"
series_category: "Local Shows"
```
### Library Configuration Parameters
| Block / Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `enabled` | Bool | `false` | Master switch to turn on the local media library feature. |
| **`scan_directories`** | List | | Folders to monitor. |
| ↳ `enabled` | Bool | `true` | Allows temporarily disabling specific folders. |
| ↳ `path` | String | | The absolute or relative path to your media directory. |
| ↳ `content_type` | Enum | `auto` | Forces classification. Options: `auto` (guess via filename), `movie`, `series`. |
| ↳ `recursive` | Bool | `true` | If true, Tuliprox crawls all subdirectories within `path`. |
| `supported_extensions` | List | `[mp4, mkv, avi, ts, ...]` | File extensions that Tuliprox considers as playable video files. |
| **`metadata`** | Object | | Instructions on how to fetch or fallback for movie details. |
| ↳ `fallback_to_filename` | Bool | `true` | If NFO or TMDB fails, uses the filename to construct basic metadata (Title, Year). |
| ↳ `read_existing.kodi` | Bool | `true` | Attempts to read Kodi-compatible `.nfo` files residing next to the media. |
| ↳ `read_existing.plex` | Bool | `false` | Attempts to read Plex metadata formats. |
| ↳ `read_existing.jellyfin` | Bool | `false` | Attempts to read Jellyfin metadata formats. |
| ↳ `formats` | List | `["nfo"]` | Metadata file formats to read when `read_existing` is enabled (e.g., `nfo`). |
*Note: For TMDB enrichments to work on local files, `metadata_update.tmdb.enabled: true` must be set in your config! The library
utilizes the exact same API limits, API keys, and caches as the IPTV streams.*
---
## Integration as an Input (`source.yml`)
To make your local movies visible in your M3U/Xtream targets, you attach the library as a standard `input` of `type: library`
in your `source.yml`:
```yaml
inputs:
- name: my_local_library
type: library
enabled: true
sources:
- inputs:
- my_local_library
- my_iptv_provider
targets:
- name: mixed_target
filter: 'Group ~ ".*"'
output:
- type: xtream
```
In this setup, the target `mixed_target` now merges the Live-TV channels from your IPTV provider and your local `.mkv` movies
into a single, clean Xtream API output for the client. The local files bypass the Reverse Proxy routing and stream directly off
your disk when requested by the IPTV player!
---
## Triggering Scans (CLI & API)
By default, the library scan can be automated using standard Cron syntax under the `schedules:` block (`type: LibraryScan`).
However, you can also force it manually.
**Via CLI (Command Line):**
```bash
# Incremental Delta-Scan (Only processes new/modified files)
./tuliprox --scan-library
# Ignores the cache and forces TMDB/PTT to re-evaluate ALL local files
./tuliprox --force-library-rescan
```
**Via REST API (e.g., triggered from a post-download script like Radarr/Sonarr):**
```http
POST /api/v1/library/scan
Content-Type: application/json
Authorization: Bearer <TULIPROX_API_TOKEN>
{"force_rescan": false}
```
**Status Polling:**
```http
GET /api/v1/library/status
```
Returns JSON information about the number of detected files, errors, and the progress of the background scan.
-488
View File
@@ -1,488 +0,0 @@
# Config Reference
`config.yml` controls the application runtime, storage, reverse proxy behavior and optional subsystems.
## Top-level entries
Common top-level fields are:
- `api`
- `storage_dir`
- `default_user_agent`
- `process_parallel`
- `messaging`
- `video`
- `metadata_update`
- `schedules`
- `backup_dir`
- `mapping_path`
- `template_path`
- `update_on_boot`
- `web_ui`
- `reverse_proxy`
- `log`
- `user_access_control`
- `connect_timeout_secs`
- `custom_stream_response_path`
- `custom_stream_response_timeout_secs`
- `hdhomerun`
- `proxy`
- `ipcheck`
- `config_hot_reload`
- `sleep_timer_mins`
- `accept_unsecure_ssl_certificates`
- `disk_based_processing`
- `library`
Relative paths are resolved against the tuliprox home directory:
`--home` -> `TULIPROX_HOME` -> directory of the `tuliprox` binary.
## Core runtime fields
### `process_parallel`
Enable this on multi-core systems if you want parallel processing.
Be aware that multiple worker threads can also consume multiple provider connections during updates.
### `api`
`api` contains the server-mode settings. Example:
```yaml
api:
host: localhost
port: 8901
web_root: ./web
```
`web_root` is resolved relative to the tuliprox home directory when it is not absolute.
### `storage_dir`
Storage location for persisted playlists, databases and metadata.
Example:
```yaml
storage_dir: ./data
```
### `mapping_path` and `template_path`
Tuliprox can load mappings and templates centrally from files or directories.
- `mapping_path`: single file or directory
- `template_path`: single file or directory
Defaults:
- mappings: `mapping.yml`
- templates: `template.yml`
If a path points to a directory, all `*.yml` files are loaded in alphanumeric order and merged.
Template names must remain globally unique.
## Provider failover and rotation
Tuliprox supports provider failover and DNS-aware rotation.
Providers can expose multiple URLs, and Tuliprox can rotate to the next candidate on supported failure conditions.
### `provider://` scheme
Configurations can reference providers via `provider://<provider_name>/...`.
Tuliprox resolves that to the current active provider URL or resolved IP.
### Automatic failover triggers
Failover is triggered for:
- network timeouts
- HTTP `408`
- HTTP `500`, `502`, `503`, `504`
- HTTP `404`, `410`, `429`
It does not trigger for `401` or `403`.
### Provider DNS resolution
Each provider can optionally enable a `dns` block with:
- `enabled`
- `refresh_secs`
- `prefer`: `system` | `ipv4` | `ipv6`
- `max_addrs`
- `schemes`
- `keep_vhost`
- `overrides`
- `on_resolve_error`
- `on_connect_error`
Resolved IPs are persisted in `provider_dns_resolved.json` below `storage_dir`.
Example:
```yaml
provider:
- name: my_provider
urls:
- http://provider-a.example
- http://provider-b.example
dns:
enabled: true
refresh_secs: 300
prefer: ipv4
schemes: [http, https]
keep_vhost: true
max_addrs: 2
on_resolve_error: keep_last_good
on_connect_error: try_next_ip
```
## `messaging`
Optional notification system for `telegram`, `discord`, `rest` and `pushover`.
`notify_on` can include:
- `info`
- `stats`
- `error`
- `watch`
Templates can be raw strings or `file://` / `http(s)://` URIs.
Handlebars variables include `message`, `kind`, `timestamp`, `stats`, `watch` and `processing`.
Example:
```yaml
messaging:
notify_on:
- info
- stats
- error
telegram:
markdown: true
bot_token: "<telegram bot token>"
chat_ids:
- "<chat id>"
- "<chat id>:<thread id>"
```
## `video`
Optional video-related behavior:
- `extensions`: file extensions treated as video content
- `download`: download integration for the Web UI
- `web_search`: URL template for media searches
Example:
```yaml
video:
web_search: "https://www.imdb.com/search/title/?title={}"
extensions: [mkv, mp4, avi]
download:
directory: /tmp
organize_into_directories: true
episode_pattern: '.*(?P<episode>[Ss]\d{1,2}(.*?)[Ee]\d{1,2}).*'
```
## `metadata_update`
Controls metadata resolve, TMDB, probing and FFprobe behavior.
Important groups:
- `log`
- `resolve`
- `probe`
- `tmdb`
- `ffprobe`
- `retry_delay`
- `worker_idle_timeout`
- `max_queue_size`
- `no_change_cache_ttl_secs`
- `probe_fairness_resolve_burst`
Key fields:
- `resolve.max_retry_backoff`
- `resolve.min_retry_base`
- `resolve.max_attempts`
- `resolve.exhaustion_reset_gap`
- `probe.cooldown`
- `probe.max_attempts`
- `probe.retry_backoff_step_1`
- `probe.retry_backoff_step_2`
- `probe.retry_backoff_step_3`
- `probe.retry_load_retry_delay`
- `probe.backoff_jitter_percent`
- `probe.user_priority`
- `tmdb.enabled`
- `tmdb.api_key`
- `tmdb.rate_limit_ms`
- `tmdb.cache_duration_days`
- `tmdb.language`
- `tmdb.cooldown`
- `tmdb.match_threshold`
- `ffprobe.enabled`
- `ffprobe.timeout`
- `ffprobe.analyze_duration`
- `ffprobe.probe_size`
- `ffprobe.live_analyze_duration`
- `ffprobe.live_probe_size`
Example:
```yaml
metadata_update:
cache_path: metadata
log:
queue_interval: 30s
progress_interval: 15s
resolve:
max_retry_backoff: 1h
min_retry_base: 5s
max_attempts: 3
exhaustion_reset_gap: 1h
probe:
cooldown: 7d
retry_load_retry_delay: 1m
retry_backoff_step_1: 10m
retry_backoff_step_2: 30m
retry_backoff_step_3: 1h
max_attempts: 3
backoff_jitter_percent: 20
user_priority: 127
```
Duration fields support `s`, `m`, `h`, `d` or plain seconds.
Size fields support `B`, `KB`, `MB`, `GB`, `TB` or plain bytes.
## `schedules`
Tuliprox schedules use cron expressions with a leading seconds field.
Example:
```yaml
schedules:
- schedule: "0 0 8 * * * *"
type: PlaylistUpdate
targets:
- m3u
- schedule: "0 0 20 * * * *"
type: LibraryScan
```
Supported schedule types:
- `PlaylistUpdate`
- `LibraryScan`
- `GeoIpUpdate`
## `reverse_proxy`
This block controls stream delivery, buffering, caching, headers, rate limiting and resource retries.
The detailed stream behavior lives in [Streaming And Proxy](../streaming-and-proxy.md).
Notable `reverse_proxy.stream` flags include:
- `retry`
- `metrics_enabled`
- `buffer`
- `throttle`
- `grace_period_millis`
- `grace_period_timeout_secs`
- `grace_period_hold_stream`
- `shared_burst_buffer_mb`
- `hls_session_ttl_secs`
- `catchup_session_ttl_secs`
## `backup_dir`
Location for configuration backups written from the Web UI.
## `update_on_boot`
If `true`, a playlist update starts automatically at application start.
## `log`
Supported fields:
- `sanitize_sensitive_info`
- `log_active_user`
- `log_level`
`log_level` can be a plain level such as `debug` or a module list such as:
```yaml
log:
log_level: hyper_util::client::legacy::connect=error,tuliprox=debug
```
## `web_ui`
Controls the Web UI itself:
- `enabled`
- `user_ui_enabled`
- `content_security_policy`
- `path`
- `player_server`
- `kick_secs`
- `combine_views_stats_streams`
- `auth`
`auth` supports:
- `enabled`
- `issuer`
- `secret`
- `token_ttl_mins`
- `userfile`
- `groupfile`
Example:
```yaml
web_ui:
enabled: true
user_ui_enabled: true
auth:
enabled: true
issuer: tuliprox
secret: "<jwt secret>"
userfile: user.txt
groupfile: groups.txt
```
Password hashes can be generated with `tuliprox --genpwd`.
### `userfile` format
The `userfile` uses an extended format that is backward compatible with the previous `username:hash` format:
```text
# username:argon2_hash[:group1,group2,...]
admin:$argon2id$v=19$... # no groups field -> defaults to "admin"
alice:$argon2id$v=19$...:viewer
bob:$argon2id$v=19$...:user_manager,source_manager
```
- Lines starting with `#` are comments, empty lines are ignored.
- The optional third field (after the second `:`) assigns group memberships.
- If the third field is missing, the user defaults to the `admin` group (backward compatibility).
- Users belonging to the `admin` group have full access; other groups are silently ignored for admin users.
- Users can belong to multiple groups; permissions are the union of all group permissions.
### `groupfile` format
The `groupfile` (default: `groups.txt` in the same directory as `userfile`) defines permission groups:
```text
# group_name:permission1,permission2,...
viewer:config.read,source.read,user.read,playlist.read,library.read,system.read,epg.read
user_manager:user.read,user.write
source_manager:source.read,source.write
config_manager:config.read,config.write
playlist_manager:playlist.read,playlist.write
full_manager:config.read,config.write,source.read,source.write,user.read,user.write,playlist.read,playlist.write,library.read,library.write,system.read,system.write,epg.read
```
- The `admin` group is built-in (always grants all permissions) and must not be defined in this file.
- If the file does not exist, only the built-in `admin` group is available.
- Groups and users can also be managed through the RBAC admin panel in the Web UI.
### Permission domains
| Domain | `.read` | `.write` |
|------------|----------------------|---------------------------------|
| `config` | view main config | edit main config |
| `source` | view sources | edit sources |
| `user` | list proxy users | create/edit/delete proxy users |
| `playlist` | view playlists | update/refresh playlists |
| `library` | view library | scan/manage library |
| `system` | view status/streams | GeoIP update, file downloads |
| `epg` | view EPG data | *(reserved for future use)* |
Write does not imply read. A group must explicitly grant both `domain.read` and `domain.write` if users need to view and edit content.
## Access control and stream fallback
### `user_access_control`
If enabled, Tuliprox checks provider or configured user status, expiration date and max-connections data.
### `connect_timeout_secs`
Defines the connect-phase timeout for provider requests.
`0` disables the connect timeout.
### `custom_stream_response_path`
Directory that contains fallback transport streams:
- `channel_unavailable.ts`
- `user_connections_exhausted.ts`
- `provider_connections_exhausted.ts`
- `low_priority_preempted.ts`
- `user_account_expired.ts`
- `panel_api_provisioning.ts`
`custom_stream_response_timeout_secs` can cap the playback duration of those fallback streams.
## Other optional sections
### `user_config_dir`
Storage location for user-specific configuration such as bouquets.
### `hdhomerun`
Enables HDHomeRun emulation for Plex, Jellyfin, Emby or TVHeadend integration.
It supports multiple virtual devices, optional lineup auth and SSDP plus SiliconDust discovery.
### `proxy`
Global outgoing proxy for upstream requests.
Supported schemes include `http`, `https` and `socks5`.
### `ipcheck`
Defines endpoints and optional regexes for public IP detection:
- `url`
- `url_ipv4`
- `url_ipv6`
- `pattern_ipv4`
- `pattern_ipv6`
### `config_hot_reload`
If enabled, mapping files and API-proxy configuration are hot reloaded.
### `library`
Local media library integration:
- recursive directory scanning
- movie vs series classification
- NFO reading and writing
- TMDB enrichment
- incremental scans
- stable virtual IDs
Example:
```yaml
library:
enabled: true
scan_directories:
- enabled: true
path: "/projects/media"
content_type: auto
recursive: true
supported_extensions: ["mp4", "mkv", "avi", "mov", "ts", "m4v", "webm"]
```
+207
View File
@@ -0,0 +1,207 @@
# 🗺️ Pillar 4: `mapping.yml` (Mapper DSL & Logic)
While the filter syntax in `source.yml` only determines *whether* a channel is let through, the Mapping Engine allows you to
perform deep, structural manipulations on the stream object *before* it is written to the final playlist.
Tuliprox utilizes a blazingly fast, embedded **DSL (Domain Specific Language)** specifically built for this purpose.
## Context: Why use the DSL?
Without the DSL, your provider dictates how your channels are named and categorized. The DSL gives you the power to completely
restructure chaotic upstream provider lists into a perfectly clean, personalized format. For example, you can extract the year
out of a messy title, rename the category based on that year, fix resolutions, or filter out specific terms globally.
## Top-level entries
```yaml
mappings:
templates:
mapping:
```
* **`templates`**: *(Legacy)* Inline templates for filter macros. Prefer [template.yml](configuration/template.md).
* **`mapping`**: A list of mapping rule objects.
## Mapping Rule Structure (`mapping`)
```yaml
mappings:
mapping:
- id: map_sports_clean
match_as_ascii: true
mapper:
- filter: 'Group ~ ".*Sports.*"'
script: |
@Group = "Live Sports HD"
counter:
- filter: 'Group ~ "Live Sports HD"'
value: 100
field: chno
modifier: assign
```
| Parameter | Type | Description |
| :--- | :--- | :--- |
| **`id`** | String | The identifier of the mapping. It is referenced in `source.yml` under the respective Target (`mapping_ids:[map_sports_clean]`). |
| **`match_as_ascii`** | Bool | **Background:** If `true`, Tuliprox normalizes (de-unicodes) values on-the-fly during regex evaluation. `Cinéma` is treated as `Cinema`. The actual assignment in the DSL, however, retains the original accents! |
| **`mapper`** | List | A list of scripts (executed sequentially) containing the DSL logic. Can optionally be gated by a `filter`. |
| **`counter`** | List | Logic for assigning channel numbers sequentially (see below). |
---
## Filter & Operator Basics
Before writing advanced scripts, you must understand the logical operators available for evaluating `filter` strings (Applies to
`source.yml` & `mapping.yml`):
* **Logical Operators:** `AND`, `OR`, `NOT`
* **Regex Match:** `~` (Tilde executes a Regex match against the specified field)
* **Type Match:** `=` (E.g., `Type = live`, `Type = vod`, `Type = series`, `Type = movie`)
**Evaluatable Fields:** `Group`, `Title`, `Name`, `Caption`, `Url`, `Genre`, `Input`, `Type`
*Example:* `((Group ~ "^DE.*") AND (NOT Title ~ ".*Shopping.*")) OR (Group ~ "^AU.*")`
---
## The Mapper DSL (`mapper`)
The language supports logical constructs, regex evaluations, and assignments. The fields of the currently processed playlist item
are always accessed using the `@` prefix.
**Readable & Writable `@Fields`:**
`@name`, `@title`, `@caption`, `@group`, `@id`, `@chno`, `@logo`, `@logo_small`, `@parent_code`, `@audio_track`, `@time_shift`,
`@rec`, `@url`, `@epg_channel_id`, `@epg_id`, `@genre`.
*(Special note on `@Caption`: Acts as an alias for Title/Name. If you write to `@Caption`, Tuliprox updates both the `Title` AND
the `Name` to the same value).*
### 1. Built-in Functions
| Function | Explanation | Example |
| :--- | :--- | :--- |
| `concat(a, b, ...)` | Concatenates multiple strings. | `concat("US \| ", @Title)` |
| `uppercase(a)` | Converts text to UPPERCASE. | `uppercase(@Group)` |
| `lowercase(a)` | Converts text to lowercase. | `lowercase(@Genre)` |
| `capitalize(a)` | Title Case (Capitalizes the first letter of words). | `capitalize(@Title)` |
| `split(a, delim)` | Splits a string and returns a Named list (iterable). | `split(@Genre, ",")` |
| `trim(a)` | Removes whitespace from the edges. | `trim(@Title)` |
| `replace(a, b, c)` | Simple text Search (b) & Replace (c). | `replace(@Title, "FHD", "")` |
| `pad(val, len, char, align)` | Pads strings/numbers. `>` (Pad left), `<` (Pad right), `^` (Center). | `pad(1, 3, "0", ">")` |
| `format(fmt, ...)` | Rust-style string formatting substituting `{}`. | `format("S{}E{}", season, ep)` |
| `template(name)` | Retrieves a macro value from `template.yml`. | `template("MY_MACRO")` |
| `number(val)` | Casts a string to a float/integer. | `number("2024")` |
| `first(list)` | Returns the first element of a Named list/Regex match. | `first(@Caption ~ "(\d+)")` |
| `print(a, b, ...)` | Logs the values to the console (Requires `trace` log level). | `print("Matched:", @Title)` |
| `add_favourite(grp)` | **Background:** Clone function! Takes the currently processed stream, changes its group to `grp`, generates a clean alias UUID, and adds the stream additionally (as a "Favorite") to the playlist. | `add_favourite("Top 10")` |
### 2. RegEx Captures & Extraction
Regular expressions are executed using the tilde `~` operator. The results are placed in a capture object. You can access them
via index (`.1`, `.2`) or via "Named Captures".
```dsl
# Extract the year from the title using named captures
info = @Title ~ "(?P<Movie>.*?)\s-\s(?P<Year>\d{4})"
# Store the matches in variables
movie_title = info.Movie
movie_year = info.Year
@Title = movie_title
```
### 3. Match Blocks (Switch-Case Logic)
A `match` block allows conditional assignments. **Crucial:** Order matters. The first block whose condition evaluates to true is
executed, and the `match` block exits.
```dsl
# Check if a regex found a specific station (e.g., FOX)
station = @Caption ~ "FOX"
result = match {
(var1, var2) => "Both variables exist",
station => "Only the station exists",
_ => "Fallback (Default)", # The underscore matches anything
}
```
### 4. Map Blocks (Dictionaries & Ranges)
Map blocks are ideal for translating hundreds of cryptic provider categories or resolutions into your own clean design.
**Mapping Texts (with Multi-Keys `|`):**
```dsl
quality = uppercase(@Caption ~ "\b(HD|FHD|4K|UHD)\b")
quality = map quality {
"SHD" | "SD" => "SD",
"1080p" | "FHD" => "FHD",
"4K" | "3840p" => "UHD",
_ => quality, # If nothing matches, keep the original
}
```
**Mapping Numbers (Ranges `..`):**
```dsl
year_text = @Caption ~ "(\d{4})\)?$"
year = number(year_text) # Cast String to Number
year_group = map year {
..2019 => "Classics (< 2020)",
2020..2025 => "New Releases",
_ => year_text,
}
```
### 5. For-Each Loops (Iterating Lists)
`for_each` iterates over Named-Results (like Regex captures or output from `split()`). Perfect for distributing movies into
multiple virtual folders based on their genres!
```dsl
genres = split(@Genre, "[,/&]")
genres.for_each((ignored_index, single_genre) => {
# For each genre in the string "Action, Drama", an alias stream is created!
add_favourite(concat("Genre - ", trim(single_genre)))
})
```
---
## Counters (Sequential Numbering) (`counter`)
Many IPTV players sort by channel number (`tvg-chno`). Counters allow sequential numbering of channels *after* they have passed
through the DSL.
```yaml
mapping:
- id: add_channel_numbers
counter:
- filter: 'Group ~ "DE Channels"'
value: 100
field: chno
modifier: assign
- filter: 'Group ~ "DE Channels"'
value: 1
padding: 3
field: title
modifier: prefix
concat: ". "
```
**Field Details:**
* `filter`: A string filter determining which streams this counter applies to.
* `value`: The starting value (e.g., start counting from `100`).
* `field`: The target field to write to (`chno`, `title`, `name`, `caption`).
* `padding`: Zero-padding (e.g., `padding: 3` turns `1` into `001`).
* `modifier`:
* `assign`: Hard overwrites the `field` with the number.
* `prefix`: Prepends the number to the field (e.g., `001. ARD HD`).
* `suffix`: Appends the number to the field.
* `concat`: The separator between the number and the original field for Prefix/Suffix (e.g., `". "`).
+247
View File
@@ -0,0 +1,247 @@
# 📖 Metadata Update & FFprobe (`metadata_update`)
This chapter covers the `metadata_update` block inside `config.yml`, which determines how aggressively or gently Tuliprox
manages background tasks for resolving metadata and technical stream properties.
Tuliprox utilizes three distinct mechanisms to ensure perfect library quality (especially for Plex/Jellyfin compatibility):
1. **Resolve (Xtream API):** Fetching missing VOD details (Cast, Director, Plot) directly from the provider's API.
2. **TMDB:** Supplementing missing Release Years and high-resolution Covers/Backdrops via The Movie Database.
3. **Probe (FFprobe):** Physically opening the stream to analyze the exact A/V codecs (HEVC, H264) and resolution.
## Top-level entries
```yaml
metadata_update:
cache_path: metadata
retry_delay: 2s
worker_idle_timeout: 1m
max_queue_size: 100000
no_change_cache_ttl_secs: 3600
probe_fairness_resolve_burst: 200
log:
resolve:
probe:
tmdb:
ffprobe:
```
### Global Options (Flat Keys)
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `cache_path` | String | `"metadata"` | Directory where TMDB cache files and metadata are stored. Relative paths are resolved against `storage_dir`. |
| `retry_delay` | String | `"2s"` | General minimum wait time when the worker encounters temporary runtime errors (e.g., socket timeout). |
| `worker_idle_timeout` | String | `"1m"` | Time of inactivity (empty queue) after which the background worker kills itself to free RAM/CPU. |
| `max_queue_size` | Int | `100000` | RAM Safety Limit: Maximum number of metadata tasks kept in memory per input simultaneously. |
| `no_change_cache_ttl_secs` | Int | `3600` | How long (seconds) a "No Change" status is cached to avoid unnecessary DB checks across subsequent playlist updates. |
| `probe_fairness_resolve_burst` | Int | `200` | After 200 consecutive Resolve tasks, 1 Probe task is forcibly prioritized so probes don't starve. |
---
## 1. Logging (`log`)
Controls background worker logging verbosity.
```yaml
metadata_update:
log:
queue_interval: 30s
progress_interval: 15s
```
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `queue_interval` | String | `"30s"` | Interval to log the queue status (pending tasks). |
| `progress_interval` | String | `"15s"` | Interval for progress reports (successful/failed resolves). |
---
## 2. API Resolve Limits (`resolve`)
Controls API requests for pure metadata (Xtream Info / TMDB).
```yaml
metadata_update:
resolve:
max_retry_backoff: 1h
min_retry_base: 5s
max_attempts: 3
exhaustion_reset_gap: 1h
```
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `max_retry_backoff` | String | `"1h"` | Maximum time limit for exponential wait between API failures. |
| `min_retry_base` | String | `"5s"` | Initial wait time on the very first failure. |
| `max_attempts` | Int (u8) | `3` | Max attempts per cycle before a resolve task is marked as "exhausted". |
| `exhaustion_reset_gap` | String | `"1h"` | Time window after a cycle completes before "exhausted" tasks are retried in the next run. |
---
## 3. FFprobe Retries & Limits (`probe`)
Controls technical stream probing retries via FFprobe.
```yaml
metadata_update:
probe:
cooldown: 7d
retry_load_retry_delay: 1m
retry_backoff_step_1: 10m
retry_backoff_step_2: 30m
retry_backoff_step_3: 1h
max_attempts: 3
backoff_jitter_percent: 20
user_priority: 127
```
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `cooldown` | String | `"7d"` | Hard lock time (cooldown) during which a broken stream is completely ignored to protect the provider. |
| `retry_load_retry_delay` | String | `"1m"` | Wait time if loading the internal `metadata_retry_state.db` fails. |
| `retry_backoff_step_1` | String | `"10m"` | Wait time after the 1st FFprobe failure. |
| `retry_backoff_step_2` | String | `"30m"` | Wait time after the 2nd FFprobe failure. |
| `retry_backoff_step_3` | String | `"1h"` | Wait time from the 3rd FFprobe failure onwards. |
| `max_attempts` | Int (u8) | `3` | Max failures to probe a stream before it enters global long-term cooldown. |
| `backoff_jitter_percent` | Int (u8) | `20` | Random time deviation in percent (Jitter) so hundreds of parallel retries don't hit the exact same second. |
| `user_priority` | Int (i8) | `127` | Priority of the probe task on the Unix Nice-Scale. `127` is the absolute lowest. A real user will instantly evict the probe task. |
---
## 4. TMDB API Integration (`tmdb`)
```yaml
metadata_update:
tmdb:
enabled: false
api_key: "YOUR_KEY"
rate_limit_ms: 250
cache_duration_days: 30
language: en-US
cooldown: 7d
match_threshold: 86
```
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `enabled` | Bool | `false` | Global master switch for TMDB resolution. |
| `api_key` | String | *(Internal)* | Your own TMDB API Key. If omitted, Tuliprox uses a built-in default key. |
| `rate_limit_ms` | Int (u64) | `250` | Throttling of TMDB API calls (in ms) to prevent TMDB IP bans. |
| `cache_duration_days` | Int (u32) | `30` | How long successful TMDB results are kept in the internal cache. |
| `language` | String | `"en-US"` | Preferred metadata language (e.g., `"de-DE"`). |
| `cooldown` | String | `"7d"` | Lock time for a movie if the TMDB search was *successful* but returned *no match* for the title. |
| `match_threshold` | Int (u16) | `86` | Minimum percentage score (Jaro-Winkler Distance) for a TMDB result to be accepted as a "Match". |
---
## 5. FFprobe Process Rules (`ffprobe`)
```yaml
metadata_update:
ffprobe:
enabled: true
timeout: 60
analyze_duration: 10s
probe_size: 10MB
live_analyze_duration: 5s
live_probe_size: 5MB
```
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `enabled` | Bool | `false` | Global master switch for ALL stream probing. Must be `true` for input flags like `probe_vod` to work. |
| `timeout` | Int (u64) | `60` | Hard timeout (in seconds) for the OS FFprobe process. Prevents zombie processes. |
| `analyze_duration` | String | `"10s"` | Passes `-analyzeduration` to FFprobe for VODs/Series. *Warning: Requires an explicit suffix (`s`, `m`)!* |
| `probe_size` | String | `"10MB"` | Passes `-probesize` to FFprobe for VODs/Series (Data Limit). |
| `live_analyze_duration` | String | `"5s"` | Stricter time limit for Live-TV streams (minimizes latency). |
| `live_probe_size` | String | `"5MB"` | Stricter data limit for Live-TV streams. |
**Important Note on FFprobe split:** The split design between `ffprobe` (VOD) and `ffprobe.live_...` is essential. VODs reside
statically on the server and can be analyzed generously. Live-TV streams, however, must respond quickly to avoid generating
unnecessary traffic and occupying the provider slot uselessly.
---
&nbsp;
## Additional Information
Tuliprox is not just a proxy; it is a highly intelligent **Playlist Processing Engine**. A core part of this is the asynchronous
update and metadata process that loads information from the provider, updates local databases, and fully automatically supplements
missing metadata.
## 1. The Complete Processing Pipeline
When a playlist update starts (via Scheduler, API, or Boot), Tuliprox runs this pipeline:
1. **Download & Cache Check:** For each configured `input`, it checks if provider data needs re-downloading (controlled by
`cache_duration`).
2. **Input Storage (B+Tree):** The raw data (M3U, Xtream categories) is written to a local, extremely fast B+Tree database
(`input_name.db`). This drastically saves RAM.
3. **Target Processing:** For each defined `target` (output playlist), data is loaded from the input and routed through the
pipeline (`processing_order`, e.g., Filter ➔ Rename ➔ Map).
4. **Metadata Resolve & Probe:** Tuliprox analyzes the filtered entries. If data is missing (e.g., TMDB IDs, Video Codecs),
these are dispatched as "Jobs" to the `MetadataUpdateManager`.
5. **Target Storage & EPG:** The finished playlist is written to the Target databases. Only then is XMLTV EPG data matched and
assigned.
---
## 2. The `MetadataUpdateManager` (Architecture)
The `MetadataUpdateManager` is an asynchronous background engine (if `resolve_background: true` is set on the input) that
prevents blocking the main playlist update.
### Architecture & Logic
* **Per-Input Worker:** A dedicated, isolated *Tokio Task (Worker)* is started for each Provider-Input. This prevents a slow
provider from blocking another.
* **Task-Merging:** If a stream requires both TMDB info and an FFprobe, they are merged into a single Task.
* **Rate-Limiting & Connection-Locks:** The manager strictly respects the `max_connections` of your input. An FFprobe (Stream
Analysis) is *only* initiated if a provider connection is free. A probe task runs at the absolute lowest priority
(`user_priority: 127`). If a real user starts streaming, the FFprobe process is **immediately aborted/preempted** to free the
slot for the user!
* **Smart Retry & Cooldown:** If a fetch or probe fails (e.g., HTTP 502), an exponential backoff with Jitter (random deviation)
kicks in. If the max attempts (`max_attempts`) are reached, the task enters a global cooldown (e.g., 7 days) to stop
harassing the provider.
* **Persistence (Retry State):** The status of failed tasks is stored locally in `metadata_retry_state.db`. A server restart
does not cause Tuliprox to immediately bombard the provider with requests for broken streams.
* **Cascading Updates:** Once a worker collects a batch of metadata, it saves it in the Input DB and immediately *cascades*
(inherits) the updates into all Target DBs, without requiring a full playlist rebuild.
---
## 3. Metadata Collection Mechanisms
### How is a stream queued for analysis?
Tuliprox checks every stream for completeness (`has_details()`). A task is queued if the switches in `inputs.options` are
active **and** one of these conditions is met:
* **Info-Resolve (VOD/Series):** Missing provider info (Cast, Plot, Director) retrievable via Xtream API
(`get_vod_info` / `get_series_info`).
* **TMDB/Date-Resolve:** Missing `tmdb_id` or `release_date`.
* **Probing (FFprobe):**
* VOD/Series: Missing technical A/V parameters (`video_codec`, `audio_codec`, `resolution`).
* Live-TV: The `last_probed_timestamp` is older than `probe_live_interval_hours`.
### Collection Engines
* **Release Year / Date (PTT):**
Tuliprox uses a highly optimized internal parser (`PTT` - Parse Torrent Title). It locally analyzes the stream name and
extracts the year (e.g., from *"My Movie (2023)"*). If this fails, it queries the TMDB API.
* **TMDB Information:**
Via TMDB API and a Jaro-Winkler distance comparison (similarity scoring), it fetches IDs, release years, covers, backdrops,
genres, directors, and actors.
* **Video & Audio (FFprobe):**
Tuliprox briefly opens the stream via `ffprobe`. It extracts and normalizes:
* *Resolution:* SD, 720p, 1080p, 1440p, 4K, 8K
* *Video:* Codec (H264, HEVC, AV1), Bit-Depth (8bit, 10bit), Dynamic Range (HDR10, Dolby Vision, HLG)
* *Audio:* Codec (AAC, AC3, EAC3, DTS, TrueHD) and Channels (2.0, 5.1, 7.1)
* Tuliprox uses these tags later for the `add_quality_to_filename` target feature
(e.g., `My Movie [2160p 4K HEVC HDR].strm`).
* **Seasons & Episodes:**
For series, the Xtream API delivers a structure of seasons and episodes. Tuliprox "flattens" these into individually
playable streams (`PlaylistItemType::Series`). Each episode is treated **individually** during probing, as codecs and
resolutions can change from episode to episode.
+35 -33
View File
@@ -1,46 +1,48 @@
# Configuration Overview
# ⚙️ Configuration & Setup (Overview)
Tuliprox is driven by a small set of files rather than one giant document.
Tuliprox intentionally avoids a gigantic, monolithic configuration file. Instead, it follows the principle of **Separation of
Concerns**. The setup consists of "5 Pillars" (files) that are logically separated.
## Main layers
## The Home-Directory Logic (Path Resolution)
- `config.yml`: server, runtime, reverse proxy, scheduling, metadata, Web UI
- `source.yml`: inputs, providers, aliases, targets
- `mapping.yml`: optional mapping logic
- `template.yml`: reusable expressions and templates
- `user.txt`: Web UI login credentials with optional group assignments
- `groups.txt`: RBAC permission group definitions (optional)
Before diving into the files, it is essential to understand how Tuliprox resolves file paths. Upon startup, Tuliprox determines
a central **Home Directory**. All relative paths in your config files (e.g., `storage_dir: ./data` or `web_root: ./web`) are
resolved strictly relative to this Home Directory.
## Practical split
The resolution order for the Home Directory is:
Use `config.yml` for:
1. **CLI Argument:** `--home` or `-H` (Highest Priority)
2. **Environment Variable:** `TULIPROX_HOME`
3. **Fallback:** The physical directory where the executed `tuliprox` binary is located.
- how the application runs
- where it stores data
- how it serves users and streams
### Default Directory Structure
Use `source.yml` for:
By default, if you just run Tuliprox in an empty folder, it will create the following structure:
- what data comes in
- how providers are grouped
- what outputs are exposed
```text
tuliprox_home/
├─ config/ # Contains config.yml, source.yml, mapping.yml, user.txt
├─ data/ # Primary storage_dir for B+Tree databases (*.db)
├─ data/backup/ # Backups initiated by the Web UI
├─ data/user/ # User-specific configurations (like favorites)
├─ downloads/ # Downloaded VODs
└─ web/ # Frontend assets for the Web UI
└─ cache/ # Cached resources
```
Use mappings/templates for:
*Example:* If your home is resolved to `/opt/tuliprox` and you define `backup_dir: ./backup` in `config.yml`, Tuliprox will
securely store backups exactly under `/opt/tuliprox/backup`.
- content shaping
- repeated logic
- reusable naming or filtering expressions
---
## Reading order for newcomers
## The 5 Pillars of Configuration
1. `config.yml`
2. `source.yml`
3. targets
4. reverse proxy settings
5. mapping/templates
To utilize Tuliprox fully, you must understand these 5 files and place them in your `config` directory:
## More detail
- [Config Reference](main-config.md)
- [Sources And Targets](sources-and-targets.md)
- [API Proxy](api-proxy.md)
| File | Responsibility | Architecture Level |
| :--- | :--- | :--- |
| **`config.yml`** | The Core System. Defines *how* Tuliprox physically runs. Sets ports, reverse proxy buffers, paths, TMDB API keys, metadata worker limits, Web UI settings, and global logging. | **Infrastructure & Engine** |
| **`source.yml`** | The Data Flow. Defines *what* goes in (Provider URLs, Xtream credentials, Panel API limits) and *what* goes out (Targets, Filter assignments, Formats like STRM or M3U). | **Data Sources & Targets** |
| **`api-proxy.yml`** | The Gateway. Defines virtual server endpoints exposed to clients (VLC, TiviMate) and handles **Access Management** (Which user can access which target? Which proxy mode and user priority is used?). | **Network & Auth** |
| **`mapping.yml`** | The Transformation. Contains a powerful, embedded DSL (Domain Specific Language) to dynamically rename streams, reassign groups, or map IDs (including counters) based on regex filters. | **Data Enrichment** |
| **`template.yml`** | The DRY Principle (Don't Repeat Yourself). Contains globally reusable Regular Expressions (Regex) and logic macros that can be invoked in `source.yml` and `mapping.yml` via `!MACRO_NAME!`. | **Structuring** |
+202
View File
@@ -0,0 +1,202 @@
# 🌊 Reverse Proxy (Streaming, Caching & Rate Limits)
This section documents the `reverse_proxy:` block inside `config.yml`. It is the most critical block for determining runtime
behavior when Tuliprox actively proxies video streams to clients (Reverse Proxy Mode), rather than just redirecting them.
It manages how Tuliprox establishes upstream connections, buffers video frames, handles sudden client disconnects, and caches
static resources like EPG images and channel logos.
## Top-level entries
```yaml
reverse_proxy:
resource_rewrite_disabled: false
rewrite_secret: A1B2C3D4E5F60718293A4B5C6D7E8F90
stream:
cache:
rate_limit:
disabled_header:
resource_retry:
geoip:
```
### General Parameters
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `resource_rewrite_disabled` | Bool | `false` | Normally, Tuliprox rewrites all image URLs in playlists to point to itself (e.g., `http://tuliprox:8901/resource/...`). If set to `true`, original URLs are kept (clients load images directly from the provider). **Warning:** Local caching will stop working if this is enabled! |
| `rewrite_secret` | String | `""` | A 32-character Hex string (16 bytes). Tuliprox encrypts/signs the original image URLs during the rewrite process. To prevent image URLs from becoming invalid after a server restart, you MUST enter a static secret here (generate via `openssl rand -hex 16`). |
---
## 1. Stream Management (`stream`)
This sub-block defines how Tuliprox maintains stream stability, buffers data, and handles HLS/Catchup session affinity.
```yaml
reverse_proxy:
stream:
retry: true
buffer:
enabled: true
size: 1024
throttle_kbps: 12500
grace_period_millis: 2000
grace_period_timeout_secs: 4
grace_period_hold_stream: true
hls_session_ttl_secs: 15
catchup_session_ttl_secs: 45
shared_burst_buffer_mb: 12
metrics_enabled: false
```
### Stream Parameters in Detail
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `retry` | Bool | `true` | **Background:** If the upstream provider unexpectedly drops the connection or a TCP network timeout occurs, Tuliprox immediately opens a new connection to the provider in the background and seamlessly pipes the new bytes to the end-client. The client's player (e.g., VLC) might stutter for a fraction of a second but will not abort playback. |
| `buffer.enabled` | Bool | `false` | Enables an asynchronous ring-buffer in RAM between the provider download stream and the client upload stream. Necessary if the provider stream is faster than the consumer can process. |
| `buffer.size` | Int | `0` | The size of the buffer in *Chunks* (1 Chunk = 8192 Bytes). A value of `1024` equals approximately 8 Megabytes of RAM per active stream. |
| `throttle_kbps` | Int | `0` | **Background:** Some players download VODs (Movies) at maximum line speed ("Bursting"). Providers often view this as abuse or scraping and will ban the IP. By throttling (e.g., to `12500` kbps), you force the download into a constant, inconspicuous flow. Supports units like `KB/s`, `MB/s`, `kbps`, `Mibps`. |
| `metrics_enabled` | Bool | `false` | **Monitoring:** If active, Tuliprox samples the live bandwidth (in kbps) and transferred bytes for every active reverse-proxied stream and pushes them via WebSockets to the Web UI. It adds a tiny bit of CPU overhead but is invaluable for debugging buffering issues. |
| `grace_period_millis` | Int | `2000` | The exact time window in ms where a temporary over-allocation is allowed (see notes on [The VLC Seek Problem](#the-vlc-seek-problem--grace-periods) for details). |
| `grace_period_timeout_secs` | Int | `4` | A hard timeout limit for overlapping "ghost sessions" to expire. |
| `grace_period_hold_stream` | Bool | `true` | Tuliprox artificially holds back the video data to the client, waiting for the grace check to finish, so it doesn't trigger the provider prematurely. |
| `hls_session_ttl_secs` | Int | `15` | Keeps the virtual provider slot open between HLS segment (`.ts`) requests to prevent provider bans for "Account Hopping". |
| `catchup_session_ttl_secs` | Int | `45` | The same session-holding principle applied to Archive/Catchup TV. See notes on section [Session TTLs for HLS & Catchup](#session-ttls-for-hls-m3u8--catchup) for details. |
| `shared_burst_buffer_mb` | Int | `12` | Minimum burst buffer size (in MB) used for shared live streams to immediately synchronize new clients without Keyframe dropouts. See notes on section [Shared Live Streams](#shared-live-streams) for details. |
---
## 2. Resource Caching (`cache`)
Tuliprox caches channel logos, posters, and EPG images on your disk so your clients don't stress the provider's servers on every
playlist reload.
```yaml
reverse_proxy:
cache:
enabled: true
size: 1GB
directory: ./cache
```
An LRU (Least Recently Used) disk cache. If it hits the limit (e.g., `1GB`), Tuliprox automatically deletes the oldest images.
Note: Fails if `resource_rewrite_disabled` is true.
---
## 3. Rate Limiting (`rate_limit`)
```yaml
reverse_proxy:
rate_limit:
enabled: true
period_millis: 500
burst_size: 10
```
Implements an IP-based Token-Bucket rate limiter. In this example, an IP can fire 10 requests immediately (`burst_size`). After
that, it receives exactly one new token every 500ms (`period_millis`). This prevents DDOS attacks from malfunctioning scrapers.
*(Ensure your upstream Nginx/Traefik passes `X-Forwarded-For` for this to work correctly!)*
---
## 4. Header Stripping (`disabled_header`)
When Tuliprox makes requests to the upstream provider, it can strip revealing headers that might expose which player you are
actually using or the fact that you are proxying traffic.
```yaml
reverse_proxy:
disabled_header:
referer_header: true # Removes the 'Referer' header
x_header: true # Removes all 'X-*' headers (like X-Real-IP)
cloudflare_header: true# Removes 'CF-*' headers
custom_header:
- my-custom-tracker
```
---
## 5. Resource Retries (`resource_retry`)
Defines how aggressively Tuliprox tries to retry failed logo or EPG downloads when proxying them from the provider.
```yaml
reverse_proxy:
resource_retry:
max_attempts: 3
backoff_millis: 250
backoff_multiplier: 1.5
failover_redirect_patterns:
- "service-abuse"
```
* **Retries:** After the first error, Tuliprox waits 250ms. After the second error, it waits `250 * 1.5 = 375ms`, and so on.
* **`failover_redirect_patterns`:** A list of Regex patterns. If an upstream resource responds with an HTTP Redirect (302)
containing these patterns (e.g., pointing to a "service-abuse" warning image from the provider), Tuliprox treats it as a
failure instead of blindly serving the abuse image to your clients.
---
## 6. GeoIP Resolution (`geoip`)
To see the country flags of connected clients in the Web UI ("Active Streams" tab), Tuliprox can resolve IP addresses locally.
```yaml
reverse_proxy:
geoip:
enabled: true
url: "https://raw.githubusercontent.com/sapics/ip-location-db/refs/heads/main/asn-country/asn-country-ipv4.csv"
```
The CSV file must have exactly 3 columns: `range_start,range_end,country_code`. (The DB is periodically updated via the
`schedules` block using the `GeoIpUpdate` task type).
---
&nbsp;
## Additional Information
## The "VLC Seek Problem" & Grace Periods
When a user fast-forwards or rewinds a VOD, the player calculates the new byte offset, drops the old TCP connection, and
immediately fires a new HTTP GET request (with a `Range` header) to Tuliprox.
**The Problem:** It takes milliseconds to seconds for the upstream provider to realize the old connection is dead. If you have
a `max_connections: 1` limit at the provider, they will view this new seek-request as a *second concurrent stream* and reject
it with an HTTP 509 (Bandwidth Exceeded) or HTTP 401 error.
**The Tuliprox Solution:**
* `grace_period_millis: 2000`: Tuliprox grants the user a temporary over-allocation (Grace) for exactly this duration.
* `grace_period_hold_stream: true`: Tuliprox artificially holds back the video data to the client, waiting for the grace check
to finish, so it doesn't trigger the provider prematurely.
* After the milliseconds expire, Tuliprox checks internally: Is the old connection truly gone now? If Yes ➔ Data flows.
If No ➔ The new connection is hard-killed (serving the `user_connections_exhausted.ts` video) because the user is actually
illegally watching twice.
* `grace_period_timeout_secs: 4`: A hard timeout limit for overlapping "ghost sessions" to expire.
## Session TTLs for HLS (`.m3u8`) & Catchup
HLS streams do not consist of an endless TCP pipe. Instead, the player downloads small `.ts` segments every few seconds
(e.g., `seg1.ts`, `seg2.ts`).
If Tuliprox released and re-acquired the provider slot for every single segment, providers would block the account for "Account
Hopping" or spam. Tuliprox simulates a continuous session:
* `hls_session_ttl_secs: 15`: After a `.ts` segment finishes downloading, the physical slot to the provider is closed, but the
"Virtual Slot" for this specific user remains reserved for 15 seconds. No other user can steal this slot during this window.
Channel switches from the same client can immediately take over the reservation.
* The same principle applies to Archive/Catchup TV (`catchup_session_ttl_secs: 45`), which shares the same fragmentation and
seeking issues.
## Shared Live Streams
Tuliprox can share a live stream (`share_live_streams: true` in the target options of `source.yml`). If 5 users watch the same
Live-TV channel, Tuliprox pulls the stream only 1x from the provider and multicasts the bytes locally to 5 clients.
To ensure a user who tunes in 10 seconds later doesn't get player errors due to missing I-Frames/Keyframes, Tuliprox
continuously keeps the last X Megabytes (`shared_burst_buffer_mb`, default `12`) in RAM. It fires this burst buffer at new
subscribers so their decoders can instantly synchronize.
+406
View File
@@ -0,0 +1,406 @@
# 🔌 Pillar 2: `source.yml` (Inputs, Panel API & Targets)
The `source.yml` is the central hub for data flows. Here you define your upstream providers (`inputs`), pool them using aliases,
configure automated reseller provisioning (`panel_api`), and define the output channels for your end devices (`targets`).
## Top-level entries
```yaml
templates:
provider:
inputs:
sources:
```
| Block | Description | Link |
| :--- | :--- | :--- |
| `templates` | *(Legacy)* Inline templates for filter macros. Prefer `template.yml`. | |
| `provider` | Provider Failover & DNS Rotation definitions. | [See section](#1-provider-failover--dns-rotation-provider) |
| `inputs` | Data Sources (Providers, Files, Batches, Library). | [See section](#2-inputs-data-sources-inputs) |
| `sources` | Routing logic combining inputs to output targets. | [See section](#3-routing--targets-sources) |
---
## 1. Provider Failover & DNS Rotation (`provider`)
Tuliprox includes a robust failover engine for unstable IPTV providers. You can define backup URLs and intelligent IP rotation.
Define a `provider` block globally in `source.yml` to specify multiple backup URLs:
```yaml
provider:
- name: my_failover_provider
urls:
- http://primary.example.com
- http://backup.example.com
dns:
enabled: true
refresh_secs: 300
prefer: ipv4 # system, ipv4, ipv6
schemes: [http, https]
keep_vhost: true
max_addrs: 2
on_resolve_error: keep_last_good # or fallback_to_hostname
on_connect_error: try_next_ip # or rotate_provider_url
overrides:
"primary.example.com":
- 203.0.113.10
```
### DNS Rotation Parameters (`provider.dns`)
| Parameter | Type | Default | Technical Impact |
| :--- | :--- | :--- | :--- |
| `refresh_secs` | Int | `300` | The interval in seconds the background task resolves the hostnames. (Minimum effective value is 10). |
| `prefer` | Enum | `system` | Which IP protocol to prefer during DNS resolution. Options: `system`, `ipv4`, `ipv6`. |
| `max_addrs` | Int | `None` | Hard limit on the number of resolved IPs to retain per host. |
| `schemes` | List | `[http, https]` | The HTTP schemes that IP connection rotation applies to. |
| `keep_vhost` | Bool | `false` | If `true`, the `Host` header retains the original `hostname[:port]`. If `false`, it uses `IP[:port]`. Essential for reverse proxies upstream! |
| `on_resolve_error` | Enum | `keep_last_good` | Policy on DNS resolution failure. Options: `keep_last_good` (uses cached IPs), `fallback_to_hostname` (clears cache, forcing host lookup). |
| `on_connect_error` | Enum | `try_next_ip` | Policy on TCP connection failure. Options: `try_next_ip` (cycles to the next resolved IP for the same host), `rotate_provider_url` (instantly fails over to the next URL in the `urls` list). |
### Failover Triggers
Tuliprox automatically switches URLs or DNS IPs on failure.
Failover **DOES** occur on:
* Network Timeouts
* HTTP 5xx errors (500, 502, 503, 504)
* HTTP 404 / 410 / 429
Failover **DOES NOT** trigger on:
* HTTP 401 / 403 (Authentication errors, to avoid rotating due to a banned account).
---
## 2. Inputs (Data Sources) (`inputs`)
An `input` represents an upstream provider or a local media library.
```yaml
inputs:
- name: my_provider
type: xtream
url: provider://my_failover_provider
username: my_user
password: my_password
enabled: true
cache_duration: 1d
persist: playlist_{}.m3u
method: GET
headers: {}
options: {}
epg: {}
aliases:[]
staged: {}
panel_api: {}
```
### Input Base Parameters
| Parameter | Type | Required | Default | Technical Impact & Background |
| :--- | :--- | :---: | :--- | :--- |
| `name` | String | Yes | | Internal reference ID for Tuliprox (e.g., `provider_alpha`). Must be strictly unique. Critical for persistent UUID generation! |
| `type` | Enum | No | `m3u` | Allowed: `m3u`, `xtream`, `library` (Local files), and `m3u_batch`/`xtream_batch` (CSV offloading). |
| `url` | String | Yes | | The Provider URL. Tuliprox supports magic scheme prefixes: `http://`, `https://`, `file://`, `batch://`, and **`provider://my_failover_provider`** (for the Failover System above). |
| `username` / `password` | String | Often | | Mandatory if `type` = `xtream`. |
| `enabled` | Bool | No | `true` | If `false`, this input is completely ignored in all processing. |
| `cache_duration` | String | No | `0` | **Crucial:** Determines how often Tuliprox actually downloads the raw list from the provider. At `1d` (1 day), Tuliprox serves from its local `.db` for 24 hours, even if you trigger hourly updates. This heavily protects against provider bans! Supports suffixes `s`, `m`, `h`, `d`. |
| `persist` | String | No | | Optional path template (e.g., `playlist_{}.m3u`) to permanently store the downloaded raw provider list locally on your disk. |
| `epg` | Object | No | | Allows mapping of external XMLTV files (see below). |
| `method` | Enum | No | `GET` | HTTP Request method for playlist downloads (`GET` or `POST`). |
| `headers` | Dict | No | | Custom HTTP headers for the download (e.g., `User-Agent: My-Player`). |
| `aliases` | List | No | | Connection pooling / Sub-accounts (see below). |
| `staged` | Object | No | | Hybrid architecture feature (see below). |
| `panel_api` | Object | No | | Automated reseller account generation (see below). |
---
### Input Options (`options`)
Controls the behavior during download and asynchronous metadata resolution (see the *Metadata Update* chapter) for this specific
provider.
| Parameter | Type | Default | Technical Impact & Background |
| :--- | :--- | :--- | :--- |
| `xtream_skip_live` / `vod` / `series` | Bool | `false` | Immediately ignores entire categories during the Xtream API download. Saves massive amounts of RAM and runtime if you only want Live-TV from a specific provider, for instance. |
| `xtream_live_stream_without_extension` | Bool | `false` | Strips `.ts` from generated stream URLs. |
| `xtream_live_stream_use_prefix` | Bool | `true` | Injects the `/live/` prefix into URLs. |
| `disable_hls_streaming` | Bool | `false` | Forces Tuliprox to play Live-TV as a raw MPEG-TS (`.ts`) stream, skipping HLS (`.m3u8`) reverse-proxy handling, and forcing direct TS endpoints. |
| `resolve_tmdb` | Bool | `false` | Enables TMDB queries for this specific input based on parsed titles to fill missing posters and release years. |
| `probe_stream` | Bool | `false` | Allows Tuliprox to open a provider connection (`max_connections`) to read A/V details (Codecs, HDR, 4K) via FFprobe. |
| `resolve_background` | Bool | `true` | Metadata scans run asynchronously in the background so the general playlist update (which blocks clients) finishes instantly. |
| `resolve_series` / `resolve_vod` | Bool | `false` | Fetches missing details like Plot or Cast via the Provider's API (`get_vod_info` / `get_series_info`). |
| `probe_series` / `probe_vod` | Bool | `false` | Allows explicit FFprobe analysis of movies or entire TV show seasons. |
| `probe_live` | Bool | `false` | Allows FFprobe to periodically tap into Live-TV streams in the background. |
| `probe_live_interval_hours` | Int | `120` | Interval after which a Live stream is re-analyzed (Important as backup streams often change resolutions). |
| `resolve_delay` / `probe_delay` | Int | `2` | **Ban Protection:** Hard wait time (in seconds) between API or Probe requests to the *same* provider! Prevents API spamming. |
---
### EPG Assignment & Smart Match (`epg`)
Tuliprox can load external XMLTV files and map them extremely intelligently (Fuzzy-Matching) to streams missing a valid EPG-ID.
```yaml
epg:
sources:
# 'auto' automatically generates the XMLTV URL from your Xtream credentials
- url: auto
priority: -2
logo_override: true
- url: http://localhost/my_custom_epg.xml
priority: 0
smart_match:
enabled: true
fuzzy_matching: true
match_threshold: 80
best_match_threshold: 99
name_prefix: { suffix: "." }
name_prefix_separator:[':', '|', '-']
strip:["3840p", "uhd", "fhd", "hd", "sd", "4k"]
normalize_regex: '[^a-zA-Z0-9\-]'
```
**Smart Match Parameters:**
| Parameter | Type | Default | Technical Impact |
| :--- | :--- | :--- | :--- |
| `fuzzy_matching` | Bool | `false` | Fallback to phonetic and Jaro-Winkler similarity matching if exact ID match fails. |
| `match_threshold` | Int | `80` | Minimum percentage score (10-100) to accept a fuzzy match. |
| `best_match_threshold` | Int | `99` | Score at which Tuliprox stops searching for better matches and immediately accepts the EPG assignment. |
| `name_prefix` | Enum | `Ignore` | How to treat extracted country prefixes (`US`, `FR`). Options: `Ignore`, `Suffix` (appends to end), `Prefix` (appends to start). Example: `{ suffix: "." }` turns `US: HBO` into `hbo.us`. |
| `name_prefix_separator` | List | `[':', '\|', '-']` | Characters used by the provider to delimit the country prefix from the channel name. |
| `strip` | List | *(HD/4K tags)* | Terms aggressively stripped from the channel name before attempting to match against the XMLTV database. |
| `normalize_regex` | String | `[^a-zA-Z0-9\-]` | Regex pattern used to clean names. Default strips all non-alphanumeric characters (except dashes). |
**How Smart-Matching works:**
If a stream is missing the `tvg-id`, Tuliprox tries to map the channel name to the XMLTV file.
If a channel is named `US: HBO HD 4K`, Tuliprox uses the `name_prefix_separator` logic. It splits at `:`, recognizes `US`
as a country code, strips "4K" and "HD", cleans the string to "hbo", and appends the `name_prefix.suffix` (`.`) ➔ The EPG
Fuzzy-Matching (using Double Metaphone phonetic encoding) now actively searches for the ID `hbo.us` in the XMLTV file!
---
### Provider Aliases (`aliases` & `batch://`)
**Why use Aliases?** If you bought 3 subscriptions from the *same* provider, you can pool them. Tuliprox merges the lists and
tracks free connections in one logical pool.
```yaml
inputs:
- type: xtream
name: my_provider
url: http://provider.net
username: sub_1
password: pw1
max_connections: 1
aliases:
- name: alias_sub_2
url: http://provider.net
username: sub_2
password: pw2
max_connections: 2
```
Tuliprox merges the lists and tracks: "For `my_provider` I have 1 + 2 = 3 free connections in one logical pool."
**Batch CSV Offloading:**
If you manage dozens of aliases, you can use `type: xtream_batch` and set the URL to `batch://./aliases.csv` to offload the
list.
The CSV format for Xtream is: `name;username;password;url;max_connections;priority;exp_date`.
*(Note: For batch inputs, the first valid row in the CSV assumes the identity/name of the root input to keep UUIDs stable).*
---
### Provider Panel API (`panel_api`)
Automates the creation of sub-accounts on the provider's reseller panel when connections are needed, and delete/ignore them
when they expire.
```yaml
panel_api:
url: 'https://panel.provider.com/api.php'
api_key: 'YOUR_ADMIN_KEY'
provisioning:
timeout_sec: 65
method: GET
probe_interval_sec: 10
cooldown_sec: 120
offset: 12h
alias_pool:
size: { min: auto, max: auto }
remove_expired: true
query_parameter:
client_new:
- { key: action, value: new }
- { key: type, value: m3u }
- { key: username, value: auto }
```
* `min: auto` & `max: auto`: Tuliprox compares the number of your active/enabled users in `api-proxy.yml` mapped to targets
of this input and generates exactly that many alias accounts via Panel API.
* `provisioning.offset`: Tuliprox doesn't wait until an account expires. `12h` means Tuliprox fires the `client_renew`
API call 12 hours before expiration during the boot/update cycle to prevent downtime.
* `remove_expired: true`: Automatically cleans up the `source.yml` or CSV files and deletes dead accounts.
* `value: auto`: Instructs Tuliprox to inject the actual runtime values (like the generated username or the globally defined
`api_key`) dynamically into the HTTP query parameters.
### Staged Inputs (`staged`)
Merge a perfectly maintained M3U file (e.g. from GitHub) for Live-TV with your Xtream Provider for VOD into a *single provider*
in Tuliprox!
**Background:** You buy a Premium Xtream account for VODs. However, the Live-TV section of this provider is terribly sorted.
But you have a perfectly maintained M3U file (e.g. found on a GitHub repository) for Live-TV.
With `staged`, you can logically merge these physical sources into a *single provider* in Tuliprox!
```yaml
staged:
enabled: true
type: m3u
url: https://github.com/m3u_list...
live_source: staged
vod_source: input
series_source: skip
```
Here, Tuliprox pulls `live` from the m3u file on GitHub url and uses it for Live (Staged source), but continues to use your
Xtream input for VOD.
---
## 3. Routing & Targets (`sources`)
This block links your inputs to specific output targets and applies transformation filters.
The Target defines the final list your clients download. Under `sources:` you link Targets with one or multiple `inputs`.
```yaml
sources:
- inputs:
- my_provider
targets:
- name: my_target
output: []
filter: 'Group ~ ".*"'
rename:[]
sort: {}
mapping: []
favourites: []
watch:[]
```
### Target Parameters
| Parameter | Type | Required | Default | Technical Impact & Background |
| :--- | :--- | :---: | :--- | :--- |
| `name` | String | Yes | | Unique Target name, appears in the delivery URL (e.g., `http://host/get.php?username=X&password=Y` delivers the target assigned to this user). |
| `enabled` | Bool | No | `true` | Skips this target during building. |
| `filter` | String | Yes | | Your global filter DSL. Allows operators like `NOT`, `AND`, `OR`. Example: `(!TEMPLATE_TRASH!) AND Type = live`. |
| `processing_order` | Enum | No | `frm` | Execution order: **F**ilter, **R**ename, **M**ap. With `rmf`, it renames first, then maps, then filters. |
| `rename` | List | No | | Simple Regex Search & Replace on specific fields (e.g., `@Group`). |
| `mapping` | List | No | | References IDs from `mapping.yml` for deep DSL logic. |
| `sort` | Object | No | | Sorting logic with Regex Sequences and Orders (`asc`, `desc`). |
| `favourites` | List | No | | Duplicates final channels into a named Fav-group after all transformations. |
| `watch` | List | No | | Regex on group names. If channels in these groups change during an update, Tuliprox generates a Messaging-Event ("Channels added/removed"). |
| `use_memory_cache` | Bool | No | `false` | Puts the entire compiled target playlist into RAM. Extreme speed advantages during M3U download by clients, but costs system memory. |
---
### Output Formats (`output`)
A Target can be exported to multiple formats simultaneously. Filter logic applies globally, but each output formats the result
differently.
**1. `xtream`:**
```yaml
output:
- type: xtream
skip_live_direct_source: true
update_strategy: instant
trakt:
api: { api_key: "XXX", version: "2", url: "https://api.trakt.tv" }
lists:
- { user: "gary", list_slug: "latest-tv", category_name: "Trending TV", content_type: series, fuzzy_match_threshold: 80 }
```
* `skip_live_direct_source`: Forces players to use Tuliprox's Xtream logic (Reverse Proxy/Redirect) instead of calling the
provider's direct bypass URL.
* `update_strategy`: `instant` writes changes to disk immediately. `bundled` queues updates to reduce Disk I/O.
* `trakt`: **Deep-Dive:** Tuliprox queries lists from Trakt.tv and searches your playlist for matching movies using Jaro-Winkler
fuzzy logic. If it finds hits, it creates a virtual VOD category in Xtream (e.g., "Trending TV") and copies the movies there!
**2. `m3u`:**
```yaml
output:
- type: m3u
filename: custom_playlist.m3u
include_type_in_url: false
mask_redirect_url: false
```
* `include_type_in_url`: If true, adds the stream type (`live`, `movie`, `series`) to the URL.
* `mask_redirect_url`: If true, uses URLs from `api_proxy.yml` for users in `redirect` proxy mode. Necessary if you have
multiple providers and want to cycle/failover in redirect mode without exposing the direct provider IP initially.
**3. `strm`:**
```yaml
output:
- type: strm
directory: /media/strm
style: plex
flat: true
add_quality_to_filename: true
cleanup: true
strm_props:["#KODIPROP:seekable=true", "#KODIPROP:inputstream=inputstream.ffmpeg"]
```
Generates local `.strm` files for Emby, Plex, or Jellyfin.
| Parameter | Type | Default | Technical Impact |
| :--- | :--- | :--- | :--- |
| `directory` | String | | **Mandatory.** The output folder on your local disk where `.strm` files will be written. |
| `style` | Enum | `kodi` | Naming convention styles for scrapers. Options: `kodi`, `plex`, `emby`, `jellyfin`. (E.g., Plex style outputs: `Movie Name (Year) {tmdb-ID}/Movie Name (Year).strm`). |
| `flat` | Bool | `false` | If true, creates a flat directory structure, skipping category/group subfolders. |
| `cleanup` | Bool | `false` | **Warning:** Deletes orphaned files from the directory that have been removed from the Target. Do not point this directly at your actual media files folder! |
| `underscore_whitespace` | Bool | `false` | Replaces all whitespaces in paths and filenames with `_`. |
| `add_quality_to_filename` | Bool | `false` | Appends tags like `[2160p 4K HEVC HDR]` to the filename. (Requires `ffprobe` probing enabled on the Input!). |
| `strm_props` | List | | Properties injected into `.strm` files to configure Kodi's internal player (e.g., `#KODIPROP:seekable=true`). |
**4. `hdhomerun`:**
```yaml
output:
- type: hdhomerun
device: hdhr1 # Must match a device name from config.yml
username: local_user # Must match a user from api-proxy.yml
use_output: xtream # m3u or xtream
```
Physically binds this Target to the simulated hardware tuner from `config.yml`. The `username` dictates which user's connection
limits and reverse proxy rules apply when Plex streams from the virtual antenna.
#### Favourites (`favourites`)
You can duplicate final, transformed channels into dedicated Favorite groups *after* all filtering and mapping is complete.
```yaml
favourites:
- cluster: series
group: "My Favourites"
filter: 'Name ~ "Cinema"'
match_as_ascii: true
```
* **`match_as_ascii`**: (Bool) Normalizes accented characters during the filter match (allowing "Cinema" to match "Cinéma").
The final output channel name retains its original accents.
#### Watch (`watch`)
Regex on group names. If channels in these groups change during an update, Tuliprox generates a Messaging-Event ("Channels
added/removed").
@@ -1,414 +0,0 @@
# Sources And Targets
`source.yml` defines upstream inputs, provider aliases and the targets Tuliprox publishes to clients.
## Top-level entries
- `templates`
- `inputs`
- `sources`
For new setups, prefer global `template_path` in `config.yml`, but inline `templates` remain supported.
## `templates`
Templates reduce repeated regular expressions and can reference other templates via `!name!`.
Example:
```yaml
templates:
- name: delimiter
value: '[\s_-]*'
- name: quality
value: '(?i)(?P<quality>HD|LQ|4K|UHD)?'
```
## `inputs`
Each input can define:
- `name`
- `type`: `m3u`, `xtream`, `library`, `m3u_batch`, `xtream_batch`
- `enabled`
- `persist`
- `url`
- `epg`
- `headers`
- `method`
- `username`
- `password`
- `panel_api`
- `cache_duration`
- `exp_date`
- `options`
- `aliases`
- `staged`
### URL schemes
Supported input URL schemes:
- `http://` / `https://`
- `file://`
- `provider://<provider_name>/...`
- `batch://...`
If an `m3u` or `xtream` input uses a `batch://` URL, Tuliprox treats it as `m3u_batch` or `xtream_batch`.
### Common input options
Important `options` fields include:
- `xtream_skip_live`
- `xtream_skip_vod`
- `xtream_skip_series`
- `xtream_live_stream_without_extension`
- `xtream_live_stream_use_prefix`
- `disable_hls_streaming`
- `resolve_tmdb`
- `probe_stream`
- `resolve_background`
- `resolve_series`
- `resolve_vod`
- `probe_series`
- `probe_vod`
- `probe_live`
- `probe_live_interval_hours`
- `resolve_delay`
- `probe_delay`
Example Xtream input:
```yaml
inputs:
- name: my_provider
type: xtream
url: http://provider.example:8080
username: user
password: secret
options:
resolve_tmdb: true
probe_stream: true
resolve_series: true
resolve_vod: true
```
### EPG configuration
Inputs can define multiple XMLTV sources with priorities and smart matching:
```yaml
epg:
sources:
- url: auto
priority: -2
logo_override: true
- url: http://localhost:3001/xmltv.php?epg_id=1
priority: -1
smart_match:
enabled: true
fuzzy_matching: true
match_threshold: 80
best_match_threshold: 99
```
### Aliases
Aliases let one logical provider input expose multiple credentials.
```yaml
inputs:
- type: xtream
name: my_provider
url: http://provider.net
username: primary
password: secret1
aliases:
- name: my_provider_2
url: http://provider.net
username: secondary
password: secret2
max_connections: 2
```
### Batch inputs
Batch inputs load aliases from CSV files.
`xtream_batch` CSV fields:
- `name`
- `username`
- `password`
- `url`
- `max_connections`
- `priority`
- `exp_date`
`m3u_batch` CSV fields:
- `url`
- `max_connections`
- `priority`
### `panel_api`
Tuliprox can provision or renew provider accounts through a panel API.
Supported operations include:
- `account_info`
- `client_info`
- `client_new`
- `client_renew`
- `client_adult_content`
Important controls:
- `alias_pool.size.min`
- `alias_pool.size.max`
- `alias_pool.remove_expired`
- `provisioning.timeout_sec`
- `provisioning.method`
- `provisioning.probe_interval_sec`
- `provisioning.cooldown_sec`
- `provisioning.offset`
Example:
```yaml
panel_api:
url: https://panel.example/api.php
api_key: "1234567890"
provisioning:
timeout_sec: 65
method: GET
probe_interval_sec: 10
cooldown_sec: 120
offset: 12h
```
### Staged inputs
`staged` lets Tuliprox read playlist data from another source during updates while still using the main input for streaming and details.
Important fields:
- `enabled`
- `type`
- `url`
- `headers`
- `method`
- `username`
- `password`
- `live_source`
- `vod_source`
- `series_source`
## `sources`
Each `source` contains:
- `inputs`
- `targets`
The `inputs` list references input names from the top-level `inputs` section.
## `targets`
Each target can define:
- `enabled`
- `name`
- `sort`
- `output`
- `processing_order`
- `options`
- `filter`
- `rename`
- `mapping`
- `favourites`
- `watch`
- `use_memory_cache`
### `output`
Supported output types:
- `xtream`
- `m3u`
- `strm`
- `hdhomerun`
Important output-specific fields:
`xtream`
- `skip_live_direct_source`
- `skip_video_direct_source`
- `skip_series_direct_source`
- `update_strategy`
- `trakt`
- `filter`
`m3u`
- `filename`
- `include_type_in_url`
- `mask_redirect_url`
- `filter`
`strm`
- `directory`
- `username`
- `underscore_whitespace`
- `cleanup`
- `style`
- `flat`
- `strm_props`
- `add_quality_to_filename`
- `filter`
`hdhomerun`
- `device`
- `username`
- `use_output`
### Target `options`
- `ignore_logo`
- `share_live_streams`
- `remove_duplicates`
- `force_redirect`
If `share_live_streams` is enabled, each shared live channel consumes buffer memory even when several clients share the same upstream stream.
### `sort`
Sorting consists of ordered rules with:
- `target`: `group` or `channel`
- `field`
- `filter`
- `order`
- `sequence`
Example:
```yaml
sort:
rules:
- target: group
order: asc
filter: Group ~ ".*"
field: group
sequence:
- '^Freetv'
- '^Shopping'
- '^Entertainment'
```
### `processing_order`
Controls the order of Filter, Rename and Map.
Valid values:
- `frm`
- `fmr`
- `rfm`
- `rmf`
- `mfr`
- `mrf`
### `filter`
Filters support `NOT`, `AND`, `OR`, regex matches and `Type` comparisons.
Fields include:
- `Group`
- `Title`
- `Name`
- `Caption`
- `Url`
- `Genre`
- `Input`
- `Type`
Example:
```text
((Group ~ "^DE.*") AND (NOT Title ~ ".*Shopping.*")) OR (Group ~ "^AU.*")
```
### `rename`
Rename rules contain:
- `field`
- `pattern`
- `new_name`
Example:
```yaml
rename:
- field: group
pattern: '^DE(.*)'
new_name: '1. DE$1'
```
### `mapping`
Targets can reference mapping IDs defined in `mapping.yml`.
### `favourites`
Allows explicit favorite groups after mapping and resolution.
Example:
```yaml
favourites:
- cluster: series
group: "My Favourites"
filter: 'Name ~ "Cinema"'
match_as_ascii: true
```
### `watch`
Watches final group names and emits notifications when those groups change.
```yaml
watch:
- 'FR - Movies \(202[34]\)'
- 'FR - Series'
```
## Example `source.yml`
```yaml
templates:
- name: ALL_CHAN
value: 'Group ~ ".*"'
inputs:
- type: xtream
name: my_provider
url: http://provider.example:8080
username: user
password: secret
sources:
- inputs:
- my_provider
targets:
- name: all_channels
output:
- type: xtream
- type: m3u
options:
ignore_logo: false
share_live_streams: true
filter: "!ALL_CHAN!"
```
+106
View File
@@ -0,0 +1,106 @@
# 🧩 Pillar 5: `template.yml` (Macros & DRY)
In a large IPTV setup, you will quickly realize that you are repeating the same regular expressions (Regex) or complex filters
(like blocking Adult content) across dozens of targets and mappings.
This leads to unreadable and highly unmaintainable configurations. Tuliprox solves this elegantly using **Templates**
(applying the DRY principle: Don't Repeat Yourself).
You define complex strings or regex patterns exactly once. Afterward, you can invoke them in all other configuration files
(like `source.yml` or `mapping.yml`) by wrapping the template name in exclamation marks: `!MACRO_NAME!`.
## Global Path (Recommended Setup)
It is highly recommended to set the `template_path` in `config.yml` to a directory rather than a single file:
```yaml
template_path: ./config/templates.d
```
Upon startup, Tuliprox reads all `.yml` files in this directory in alphanumeric order (e.g., `01-regex.yml`, `02-filters.yml`)
and merges them into one massive global macro catalog.
*Important: The names of the templates (`name`) must be globally unique across all files!*
---
## Top-level entries
```yaml
templates:
- name: DELIMITER
value: '[\s_-]*'
```
## Structure & Variable Resolution
```yaml
templates:
# A simple regex snippet for delimiters (spaces, underscores)
- name: DELIMITER
value: '[\s_-]*'
# A capture-group regex for common TV qualities
- name: QUALITY
value: '(?i)(?P<quality>HD|LQ|4K|UHD)?'
# A nested logical filter condition
- name: FILTER_NO_TRASH
value: 'NOT (Group ~ "(?i).*Shopping.*" OR Group ~ "(?i).*Commercials.*")'
# The Magic: Macros can call other Macros!
- name: FILTER_DE_CLEAN
value: 'Group ~ "^DE.*" AND !FILTER_NO_TRASH!'
# Lists for Sequence-Sorting
- name: CHAN_SEQ
value:
- '(?i)\bUHD\b'
- '(?i)\bFHD\b'
```
Tuliprox recursively resolves the entire template tree during system startup.
*(Security Feature: The system detects cyclic dependencies—Macro A calls Macro B, which calls Macro A—and aborts the startup
with a log error to prevent infinite loops).*
---
## Practical Application
### 1. In `source.yml` (As a Target Filter)
Instead of writing a monstrous 500-character line into your target, you build it out of logical template blocks.
```yaml
targets:
- name: clean_german_tv
filter: "!FILTER_DE_CLEAN! AND Type = live"
```
### 2. In `source.yml` (As a Sequence Sort)
For the "Sort Sequence" feature (sorting by the occurrence of tags in the name), templates defined as lists (`value:` as an array)
can be injected directly into the sequence array.
```yaml
sort:
rules:
- target: channel
field: caption
order: asc
sequence:
- "!CHAN_SEQ!"
- '(?i)\bHD\b'
```
### 3. In `mapping.yml` (As a Regex Component)
In the Mapper DSL, Tuliprox injects the resolved regex pattern exactly where the exclamation mark macro is placed. This prevents complex regex typos.
```dsl
# Extracts "UHD" from "Sky Sport UHD" and writes it to the variable 'quality'
quality = uppercase(@Caption ~ "!QUALITY!")
# Replaces all arbitrary spaces and underscores with a clean separator
@Title = replace(@Title, "!DELIMITER!", " - ")
```
-134
View File
@@ -1,134 +0,0 @@
# Deployment
## Backend and frontend
Tuliprox consists of:
- a Rust backend
- a Yew/WebAssembly frontend
- static assets served from the configured web root
## Documentation delivery
The recommended documentation workflow is:
1. write docs as Markdown in `docs/src`
2. generate static HTML with `mdBook` into `frontend/build/docs`
3. copy the generated site into `frontend/dist/static/docs` during the web build
That gives you editable source files in Git without committing hand-written HTML.
## Why `mdBook`
For this repository, `mdBook` is the most pragmatic choice because it:
- keeps source in Markdown
- generates static HTML
- fits naturally into a Rust-based project
- avoids the overhead of a full Node/React documentation stack
## Main build commands
Build docs only:
```bash
make docs
```
Preview docs locally:
```bash
make docs-serve
```
Build frontend plus docs together:
```bash
make web-dist
```
This does:
1. `mdbook build` -> `frontend/build/docs`
2. `trunk build` -> `frontend/dist`
3. copy docs -> `frontend/dist/static/docs`
## Docker build
Build the project image manually:
```bash
docker build --rm -f docker/Dockerfile -t tuliprox .
```
For the local development image, adapt your compose file to point to the locally built image.
## Static binary builds
Recommended musl build:
```bash
cross build -p tuliprox --release --target x86_64-unknown-linux-musl
```
Manual prerequisite install on Debian or Ubuntu:
```bash
rustup update
sudo apt-get install pkg-config musl-tools libssl-dev
rustup target add x86_64-unknown-linux-musl
```
Then:
```bash
cargo build -p tuliprox --release --target x86_64-unknown-linux-musl
```
## Multi-platform helper scripts
The repository ships helper scripts under `bin/`:
- `bin/build_docs.sh`
- `bin/build_fe.sh`
- `bin/build_local.sh`
- `bin/build_docker.sh`
- `bin/release.sh`
`bin/build_fe.sh` is the shared entry point for docs plus frontend assets.
Wasm optimization is handled by Trunk during the build (via `data-wasm-opt` in `index.html`).
Trunk requires a compatible `wasm-opt` in `PATH`.
Recommended local setup:
```bash
./bin/install_wasm_tools.sh 128
export PATH="$PWD/.tools/wasm-tools/version_128/bin:$PATH"
./bin/build_fe.sh release
```
## Healthcheck
Tuliprox supports:
```bash
tuliprox --healthcheck
```
This can be wired into Docker healthchecks.
## Cross-compilation notes
Windows:
```bash
rustup target add x86_64-pc-windows-gnu
cargo build -p tuliprox --release --target x86_64-pc-windows-gnu
```
Raspberry Pi / armv7:
```bash
cross build -p tuliprox --release --target armv7-unknown-linux-musleabihf
```
-162
View File
@@ -1,162 +0,0 @@
# Examples And Recipes
This page collects the practical examples that previously lived in the monolithic README.
## Xtream provider quick setup
Provider data:
- URL: `http://fantastic.provider.xyz:8080`
- username: `tvjunkie`
- password: `junkie.secret`
Minimal `config.yml`:
```yaml
api:
host: 0.0.0.0
port: 8901
web_root: ./web
storage_dir: ./data
update_on_boot: true
```
Minimal `source.yml`:
```yaml
templates:
- name: ALL_CHAN
value: 'Group ~ ".*"'
inputs:
- type: xtream
name: my_provider
url: http://fantastic.provider.xyz:8080
username: tvjunkie
password: junkie.secret
sources:
- inputs:
- my_provider
targets:
- name: all_channels
output:
- type: xtream
filter: "!ALL_CHAN!"
```
Minimal `api-proxy.yml`:
```yaml
server:
- name: default
protocol: http
host: 192.168.1.41
port: 8901
timezone: Europe/Berlin
message: Welcome to tuliprox
user:
- target: all_channels
credentials:
- username: xt
password: xt.secret
proxy: redirect
server: default
```
## Filtering examples
Include all categories:
```text
Group ~ ".*"
```
Only shopping:
```text
Group ~ "(?i).*Shopping.*"
```
Exclude shopping:
```text
NOT(Group ~ "(?i).*Shopping.*")
```
More complex example:
```text
(Group ~ "^FR.*" AND NOT(Group ~ "^FR.*SERIES.*" OR Group ~ "^DE.*EINKAUFEN.*")) OR (Group ~ "^AU.*")
```
## Template-based filter composition
```yaml
templates:
- name: NO_SHOPPING
value: 'NOT(Group ~ "(?i).*Shopping.*" OR Group ~ "(?i).*Einkaufen.*")'
- name: GERMAN_CHANNELS
value: 'Group ~ "^DE: .*"'
- name: FRENCH_CHANNELS
value: 'Group ~ "^FR: .*"'
- name: MY_CHANNELS
value: '!NO_SHOPPING! AND (!GERMAN_CHANNELS! OR !FRENCH_CHANNELS!)'
```
## Excluding a single channel
```text
NOT(Title ~ "FR: TV5Monde")
```
Or only inside one group:
```text
NOT(Group ~ "FR: TF1" AND Title ~ "FR: TV5Monde")
```
## VLC seek problem with `user_access_control`
Seeking can generate very fast reconnects and byte-range requests.
If stale provider connections have not yet disappeared, the user can briefly appear above `max_connections`.
Typical mitigation:
```yaml
reverse_proxy:
stream:
grace_period_millis: 2000
grace_period_timeout_secs: 5
```
## Enable per-stream metrics in the Web UI
To show live bandwidth and transferred bytes in the streams table, enable stream metrics:
```yaml
reverse_proxy:
stream:
metrics_enabled: true
```
This is useful for operator troubleshooting and live monitoring of active reverse-proxied streams.
## Local library CLI examples
```bash
./tuliprox --scan-library
./tuliprox --force-library-rescan
./tuliprox --dbx /opt/tuliprox/data/all_channels/xtream/video.db
./tuliprox --dbm /opt/tuliprox/data/all_channels/m3u.db
./tuliprox --dbe /opt/tuliprox/data/all_channels/xtream/epg.db
```
## Custom fallback video generation
You can turn a still image into a `.ts` fallback video:
```bash
ffmpeg -y -nostdin -loop 1 -framerate 30 -i blank_screen.jpg -f lavfi \
-i anullsrc=channel_layout=stereo:sample_rate=48000 -t 10 -shortest -c:v libx264 \
-pix_fmt yuv420p -preset veryfast -crf 23 -x264-params "keyint=30:min-keyint=30:scenecut=0:bframes=0:open_gop=0" \
-c:a aac -b:a 128k -ac 2 -ar 48000 -mpegts_flags +resend_headers -muxdelay 0 -muxpreload 0 -f mpegts blank_screen.ts
```
+175
View File
@@ -0,0 +1,175 @@
# 🧪 Examples, Recipes & Ecosystem Stacks
This chapter provides concrete copy & paste solutions for common scenarios spanning the different pillars of the Tuliprox architecture, concluding
with the ultimate Docker deployment stack.
## 1. Quickstart: Minimal Xtream Setup
You want to simply "pass through" your Xtream provider while utilizing the Web UI.
**Minimal `config.yml`:**
```yaml
api:
host: 0.0.0.0
port: 8901
web_root: ./web
storage_dir: ./data
update_on_boot: true
```
**Minimal `source.yml`:**
```yaml
templates:
- name: ALL_CHAN
value: 'Group ~ ".*"'
inputs:
- type: xtream
name: my_provider
url: http://fantastic.provider.xyz:8080
username: tvjunkie
password: junkie.secret
sources:
- inputs:
- my_provider
targets:
- name: clean_list
output:
- type: xtream
filter: "!ALL_CHAN!" # Lets everything through
```
**Minimal `api-proxy.yml`:**
```yaml
server:
- name: default
protocol: http
host: 192.168.1.41
port: 8901
timezone: Europe/Berlin
message: Welcome to tuliprox
user:
- target: clean_list
credentials:
- username: me
password: mypass
proxy: redirect # Tuliprox does not proxy the stream, it just redirects the player
server: default
```
---
## 2. Advanced Filtering (Exclusion Logic)
DSL filters are applied in `source.yml` at the `target` level. Regular expressions require the tilde `~` operator.
**Allow if the word "Shopping" is included (Case-Insensitive):**
```text
Group ~ "(?i).*Shopping.*"
```
**Reverse the logic (No Shopping!):**
```text
NOT (Group ~ "(?i).*Shopping.*")
```
**Complex Matrix (Bracket Logic):**
Allow everything from DE and FR, except Series and Commercials. Always allow Australia.
```text
(Group ~ "^(DE|FR).*" AND NOT (Group ~ "(?i).*SERIES.*" OR Group ~ "(?i).*COMMERCIAL.*")) OR (Group ~ "^AU.*")
```
**Filtering a specific channel, even if it appears in multiple groups:**
```text
NOT (Title ~ "FR: TV5Monde" AND Group ~ "FR: TF1")
```
---
## 3. Generating Custom Fallback Videos (FFmpeg)
If a provider stream returns a 404 or the user hits their limit, it is much more elegant to play an info video ("Channel Offline") than letting
the player hang endlessly on a dropped HTTP connection. Tuliprox searches the `custom_stream_response_path` folder for exactly named `.ts` files
(MPEG-TS).
You can turn a simple image (`blank_screen.jpg`) into a clean, 10-second fallback stream with a silent audio track (crucial for player A/V
sync!) using FFmpeg:
```bash
ffmpeg -y -nostdin -loop 1 -framerate 30 -i blank_screen.jpg -f lavfi \
-i anullsrc=channel_layout=stereo:sample_rate=48000 -t 10 -shortest -c:v libx264 \
-pix_fmt yuv420p -preset veryfast -crf 23 -x264-params "keyint=30:min-keyint=30:scenecut=0:bframes=0:open_gop=0" \
-c:a aac -b:a 128k -ac 2 -ar 48000 -mpegts_flags +resend_headers -muxdelay 0 -muxpreload 0 -f mpegts channel_unavailable.ts
```
Move `channel_unavailable.ts` into your resources folder. Tuliprox will loop this file from RAM until the user switches channels or the
`custom_stream_response_timeout_secs` limit is reached.
---
## 4. The Ultimate Docker Ecosystem Stack
Running Tuliprox bare-metal is fine, but running it behind a modern DevSecOps stack provides SSL termination, VPN routing to bypass ISP
blocks, and firewall protection against brute-force attacks.
In the repository under `docker/container-templates/`, you will find ready-to-use Docker Compose files.
### Components of the Stack
1. **Traefik (Reverse Proxy):** Handles incoming port 443 traffic, auto-renews Let's Encrypt certificates (via Cloudflare DNS-01 challenge), and
applies strict Content-Security-Policy headers.
2. **Gluetun (VPN Egress):** A WireGuard/OpenVPN client container. It connects to Mullvad/ProtonVPN. We attach a **Socks5 Sidecar** to its network
namespace. Tuliprox can then be configured to route its upstream provider requests through this Socks5 proxy, completely hiding your server's IP
from the IPTV provider.
3. **CrowdSec (WAF & Bouncer):** Analyzes Traefik access logs in real-time. If someone tries to brute-force your Tuliprox Web UI or run
path-traversal attacks, CrowdSec instructs the Traefik Bouncer plugin to drop their IP at the edge.
### How to wire it up
**1. Create the Docker Networks:**
```bash
docker network create proxy-net
docker network create crowdsec-net
```
**2. Configure Gluetun & Socks5:**
In `container-templates/gluetun/gluetun-01/.env.wg-01`, add your Wireguard details.
In `container-templates/gluetun/.env.socks5-proxy`, set user/pass for the proxy.
Start it: `docker-compose up -d`. It exposes port 1388 internally.
**3. Configure Tuliprox to use the VPN:**
In your `config.yml`, point the global proxy setting to the Socks5 container:
```yaml
proxy:
url: socks5://socks5-01:1388
username: "<socks5-proxy-user>"
password: "<socks5-proxy-password>"
```
*Note: Ensure Tuliprox and the Socks5 container share the `proxy-net` network.*
**4. Protect Tuliprox with Traefik Labels:**
In your Tuliprox `docker-compose.yml`, add the Traefik labels to route traffic securely:
```yaml
labels:
- "traefik.enable=true"
- "traefik.http.routers.tuliprox-secure.entrypoints=websecure"
- "traefik.http.routers.tuliprox-secure.rule=Host(`tv.yourdomain.com`)"
- "traefik.http.routers.tuliprox-secure.tls=true"
- "traefik.http.routers.tuliprox-secure.tls.certresolver=cloudflare"
# Attach CrowdSec Bouncer and Security Headers
- "traefik.http.routers.tuliprox-secure.middlewares=cs-bouncer-traefik-plugin@file,default-security-headers@file"
- "traefik.http.services.tuliprox.loadbalancer.server.port=8901"
```
This architecture ensures your IPTV provider only sees the VPN IP, your clients only see your secure `tv.yourdomain.com` domain, and malicious
bots are blocked instantly by CrowdSec before they even reach the Rust backend.
+11 -17
View File
@@ -15,14 +15,12 @@ Tuliprox can publish:
- M3U
- Xtream-style outputs
- HDHomeRun
- STRM
- STRM Files
That makes it usable both for IPTV players and for media-server-oriented workflows.
## Playlist processing
Tuliprox can:
- filter channels and groups
- rename or normalize entries
- apply mappings and templates
@@ -31,8 +29,6 @@ Tuliprox can:
## Runtime streaming
Tuliprox can:
- reverse-proxy streams instead of redirecting them
- keep provider account affinity where clients need it
- share live streams across users
@@ -42,13 +38,21 @@ Tuliprox can:
## Metadata and library
Tuliprox can:
- resolve VOD and series metadata
- probe stream capabilities
- scan local media
- combine local library content with IPTV-oriented outputs
## Operational features
Tuliprox also includes:
- scheduled playlist refreshes
- hot config reload support
- provider failover and DNS-aware connection rotation
- notifications and monitoring hooks
- **Web UI** with monitoring and web-based configuration ability
## Access control
Tuliprox supports role-based access control (RBAC) for the Web UI:
@@ -59,13 +63,3 @@ Tuliprox supports role-based access control (RBAC) for the Web UI:
- compact bitmask encoding in JWT claims for low-overhead permission checks
- backward-compatible user file format
- Web UI admin panel for user and group management
## Operational features
Tuliprox also includes:
- scheduled playlist refreshes
- hot config reload support
- provider failover and DNS-aware connection rotation
- Web UI
- notifications and monitoring hooks
+52 -42
View File
@@ -1,5 +1,56 @@
# Getting Started
## Recommended reading order
1. [Installation](installation.md)
2. [Configuration (Core System)](configuration/config.md)
3. [Add Sources & Targets](configuration/source.md)
4. [API Proxy](configuration/api-proxy.md)
5. [Streaming & Proxy](configuration/reverse-proxy.md)
6. [Templates](configuration/template.md)
7. [Mappings](configuration/mapping-dsl.md)
## Run Tuliprox via docker compose
```yaml
services:
tuliprox:
container_name: tuliprox
image: ghcr.io/euzu/tuliprox-alpine:latest
working_dir: /app
volumes:
- /opt/tuliprox/config:/app/config
- /opt/tuliprox/data:/app/data
- /opt/tuliprox/backup:/app/backup
- /opt/tuliprox/downloads:/app/downloads
- /opt/tuliprox/cache:/app/cache
environment:
- TZ=Europe/Berlin
ports:
- "8901:8901"
restart: unless-stopped
healthcheck:
test: ["CMD", "/app/tuliprox", "-p", "/app/config", "--healthcheck"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
```
Open the Web UI afterward and continue with the configuration.
### First configuration steps
For a new setup, the usual first goal is:
1. add one working input
2. create one target
3. confirm playlist output
4. confirm one stream works
5. only then add mapping, filtering, reverse proxy and metadata features
That keeps failures local and makes provider-specific issues much easier to diagnose.
## Run modes
Tuliprox has two main modes:
@@ -81,47 +132,6 @@ Typical directories below that home:
- `data/backup/`
- `downloads/`
- `web/`
- `cache/`
All relative paths in the configuration are resolved against that home directory.
## Quick Docker start
```yaml
services:
tuliprox:
container_name: tuliprox
image: ghcr.io/euzu/tuliprox-alpine:latest
working_dir: /app
volumes:
- /home/tuliprox/tuliprox:/app/tuliprox
- /home/tuliprox/config:/app/config
- /home/tuliprox/data:/app/data
- /home/tuliprox/cache:/app/cache
environment:
- TZ=Europe/Paris
ports:
- "8901:8901"
restart: unless-stopped
```
Open the Web UI afterwards and continue with the configuration.
## Good first milestone
For a new setup, the usual first goal is:
1. add one working input
2. create one target
3. confirm playlist output
4. confirm one stream works
5. only then add mapping, filtering, reverse proxy and metadata features
That keeps failures local and makes provider-specific issues much easier to diagnose.
## Recommended reading order
1. [Config Reference](configuration/main-config.md)
2. [Sources And Targets](configuration/sources-and-targets.md)
3. [API Proxy](configuration/api-proxy.md)
4. [Streaming And Proxy](streaming-and-proxy.md)
5. [Mapping And Templates](mapping-and-templates.md)
+86 -31
View File
@@ -1,42 +1,97 @@
# tuliprox
# 📖 Tuliprox Operator Manual: Introduction & Architecture
`tuliprox` is an IPTV proxy and playlist processor for people who need more control than a raw provider playlist can offer.
Welcome to the **Tuliprox Operator Manual**.
It sits between provider inputs and client applications and gives you a place to:
**Tuliprox** is a highly optimized, resource-efficient IPTV proxy and playlist processor written in Rust 🦀. It acts as an intelligent middleware
layer between your upstream IPTV providers (or local hard drives) and your local media clients (Plex, Jellyfin, Emby, Kodi, TiviMate, etc.).
- clean up channels
- normalize outputs
- protect provider accounts
- manage user access
- handle HLS, catchup and shared streaming behavior
- mix IPTV with a local media library
## System Architecture
## What makes it different
To ensure maximum performance and a modern user experience, Tuliprox consists of three core components:
Tuliprox is not only a playlist rewriter.
It is also a runtime stream broker with provider-aware logic.
* **Rust Backend:** A high-performance, asynchronous engine handling stream brokering, authentication, and playlist processing.
* **Yew/WebAssembly Frontend:** A reactive, browser-based management interface compiled to WASM for near-native performance.
* **Static Assets:** All necessary resources are served directly from the configured web root, making the deployment self-contained and easy to proxy.
That matters when you need things like:
## Why Tuliprox?
- connection limits per user
- provider account reuse for HLS or catchup
- priority-based stream preemption
- shared live streams
- custom fallback videos when a stream cannot be delivered
Unlike simple scripts that merely perform text replacement on M3U files (such as m3u4u or xTeVe), Tuliprox is a full-fledged **Runtime Stream Broker**.
It actively intercepts video traffic, negotiates connections with providers, protects your accounts from bans, and enhances metadata locally.
Most IPTV providers restrict access via a strict `max_connections` limit. When users fast-forward, rewind, or rapidly switch channels in modern
IPTV players (like VLC or TiviMate), "ghost connections" are often created. The provider sees multiple active streams from the same IP, leading to
immediate HTTP 509 (Bandwidth Exceeded) errors or account bans.
Tuliprox solves this through a highly complex **Reverse-Proxy Streaming Engine**:
1. **Session Holding (HLS/Catchup):** Tuliprox artificially holds provider slots open while the client negotiates or switches between `.m3u8`
segments, preventing "account hopping" bans.
2. **Grace Periods:** It mitigates the "VLC seek problem" by granting temporary grace periods, holding the video stream back until old connections
to the provider are safely terminated.
3. **Shared Streams:** If multiple users watch the same live event, Tuliprox opens only *one* connection to the upstream provider and multicasts the
traffic locally to all clients, saving valuable provider slots and bandwidth.
4. **Preemption (Priority Routing):** You can assign priorities to users. If all provider slots are occupied, Tuliprox automatically kicks the least
important user (e.g., a background metadata scan or a user with priority 100) to free up the stream for an admin (priority 0).
Tuliprox downloads raw lists from the provider, stores them in lightning-fast, local **B+Tree databases** (to save RAM).
To perfectly prepare playlists for tools like Plex or Jellyfin, metadata (covers, release years, TMDB IDs, video codecs) is often missing from
provider sources.
Tuliprox dispatches asynchronous worker tasks for **Metadata Enrichment**:
* Parse titles locally (via PTT - Parse Torrent Title) to extract release years.
* Query the **TMDB API** to fetch high-resolution covers, backdrops, and cast information.
* Utilize local **FFprobe** to look directly into the provider's stream to extract precise technical data like Codecs (HEVC, H264), HDR formats
(Dolby Vision), and Audio Channels (5.1).
## Network Flow Architecture
Tuliprox integrates seamlessly into modern DevSecOps environments. Here is a high-level view of a recommended production stack (which we cover
extensively in the *Examples, Recipes & Ecosystem Stacks* chapter):
```mermaid
sequenceDiagram
participant Client as Media Client (Plex/TiviMate)
participant WAF as CrowdSec (AppSec/Firewall)
participant RP as Traefik (Reverse Proxy)
participant Tuli as Tuliprox (Stream Broker)
participant VPN as Gluetun (WireGuard/Socks5)
participant Prov as Upstream IPTV Provider
Client->>RP: Request Stream (HTTPS)
RP->>WAF: Validate Request (Bouncer)
WAF-->>RP: Allow
RP->>Tuli: Forward Request (HTTP)
alt Connection Limit Reached?
Tuli->>Tuli: Check Preemption & Grace Period
Tuli-->>Client: Serve "Exhausted" Custom Video
else Slot Available
Tuli->>VPN: Fetch Stream via Socks5/HTTP Proxy
VPN->>Prov: Encrypted WireGuard Tunnel
Prov-->>VPN: MPEG-TS / HLS Stream
VPN-->>Tuli:
Tuli->>Tuli: Buffer & Throttle Stream
Tuli-->>RP: Piped Video Stream
RP-->>Client: Seamless Playback
end
```
## Documentation map
- [Getting Started](getting-started.md): first run, main commands, file layout
- [Core Features](features.md): what tuliprox can do at a high level
- [Configuration](configuration/overview.md): config file layout and field reference
- [Sources And Targets](configuration/sources-and-targets.md): inputs, aliases, outputs and processing
- [API Proxy](configuration/api-proxy.md): users, servers, reverse/redirect and access URLs
- [Streaming And Proxy](streaming-and-proxy.md): runtime behavior, HLS/catchup affinity and reverse-proxy options
- [Mapping And Templates](mapping-and-templates.md): DSL, mapping files, counters and grouping examples
- [Deployment](deployment.md): build and ship backend, frontend and docs
- [Examples And Recipes](examples-and-recipes.md): practical setup and filtering examples
This Operator Manual will guide you through every single configuration variable to help you extract the maximum potential from this engine.
## Source format
The documentation source is plain Markdown in `docs/src`.
Static HTML is generated from it with `mdBook` and can be shipped together with the Web UI under `/static/docs/`.
* [Build & Deploy](./build-and-deploy.md)
* **[Installation](./installation.md)**
* **[Configuration Overview](./configuration/overview.md)**
* [Main Config](./configuration/config.md)
* [Streaming & Proxy Behavior](./configuration/reverse-proxy.md)
* [Metadata Update & Probe](./configuration/metadata-update.md)
* [Local Library](./configuration/local-library.md)
* [Sources & Targets](./configuration/source.md)
* [API Proxy](./configuration/api-proxy.md)
* [Templates](./configuration/template.md)
* [Mapping](./configuration/mapping-dsl.md)
* **[Examples & Recipes](./examples-recipes.md)**
* [Operations & Debugging](./operations-debugging.md)
* [Troubleshooting & Resilience](./troubleshooting.md)
+151
View File
@@ -0,0 +1,151 @@
# 🚀 Installation (Docker & Binaries)
Tuliprox is designed to run flawlessly in modern DevOps infrastructures (Docker/Kubernetes) but can also be executed as a standalone binary on Linux,
Windows, or macOS for lightweight setups.
This guide covers the installation using precompiled artifacts. If you want to compile Tuliprox from source, create custom Docker images, or build
the documentation yourself, please refer to the [Build & Deploy (For Professionals)](build-and-deploy.md) chapter.
---
## 1. Running via Docker (Recommended)
The recommended way for production use is Docker. Tuliprox does not require external database containers (everything uses internal embedded `.db`
B+Tree files), making the setup extremely lightweight. You can pull the ready-to-use images directly from the GitHub Container Registry
(`ghcr.io`).
### The Ideal `docker-compose.yml`
```yaml
services:
tuliprox:
container_name: tuliprox
image: ghcr.io/euzu/tuliprox-alpine:latest
user: "133:144" # (Recommended) Run as non-root User (UID:GID of your host system)
working_dir: /app
volumes:
- /opt/tuliprox/config:/app/config
- /opt/tuliprox/data:/app/data
- /opt/tuliprox/backup:/app/backup
- /opt/tuliprox/downloads:/app/downloads
- /opt/tuliprox/cache:/app/cache
environment:
- TZ=Europe/Berlin
ports:
- "8901:8901"
restart: unless-stopped
healthcheck:
test: ["CMD", "/app/tuliprox", "-p", "/app/config", "--healthcheck"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
```
### Volume Mapping (Technical Background)
The separation of volumes is critical for security and performance:
| Mount Point | Explanation & Technical Background |
|:-----------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `/app/config` | Must contain your YAML files (`config.yml`, `source.yml`, etc.). Monitored by Tuliprox for file system events (`config_hot_reload`). |
| `/app/data` | The `storage_dir`. Contains **Runtime Data**. Tuliprox recursively creates B+Tree databases (`*.db`), M3U caches, and TMDB metadata here. **THIS MUST BE PERSISTENT!** If lost during a restart, Tuliprox will attempt to redownload and FFprobe tens of thousands of streams, inevitably leading to provider bans. |
| `/app/backup` | Destination folder for configuration backups triggered via the Web UI. |
| `/app/downloads` | Destination folder for local video downloads initiated via the Web UI. |
| `/app/cache` | Destination folder for local image downloads initiated via the xtream codes or m3u api use. |
### Docker Image Variants (`scratch` vs. `alpine`)
Two distinct flavors are available in the container registry:
1. **`scratch-final` (`ghcr.io/euzu/tuliprox:latest`)**:
This is an extremely hardened, minimalist image (`FROM scratch`). It contains *only* the statically linked Tuliprox binary, FFmpeg/FFprobe
binaries, and CA certificates (for HTTPS/TMDB requests). There is no shell (`/bin/sh`), no package manager, and no system libraries.
**Advantage:** Maximum security and minimal attack surface (DevSecOps Best Practice).
**Disadvantage:** You cannot `docker exec` into the container for manual debugging.
2. **`alpine-final` (`ghcr.io/euzu/tuliprox-alpine:latest`)**:
Based on Alpine Linux. Contains a shell, Tini (init system), and basic tools.
**Advantage:** Ideal for debugging. You can `docker exec -it tuliprox sh` to inspect logs or run manual `curl`/`ffprobe` tests from within the
container's network namespace to verify provider blocks.
---
## 2. Precompiled Standalone Binaries
If you prefer running Tuliprox bare-metal without Docker, you can download the precompiled binaries for your operating system (Linux, Windows,
macOS, ARM).
1. Go to the [Releases page on GitHub](https://github.com/euzu/tuliprox/releases).
2. Download the archive matching your OS and Architecture.
3. Extract the binary and place it in your desired home directory.
4. Run it via the CLI commands detailed below.
---
## 3. CLI Usage & Runtime Modes
Tuliprox can operate in two primary modes. Running the compiled binary directly is the best way to test configurations locally before deploying
them to production.
### Server Mode (Persistent Operation)
Run Tuliprox as a persistent IPTV proxy server:
```bash
./tuliprox -s -c config/config.yml -i config/source.yml
```
This mode enables:
* The Web UI Dashboard
* The active Reverse-Proxy Streaming Engine
* API endpoints (Xtream/M3U for players)
* Background Workers (Metadata Scanner, DNS Resolver, Scheduler)
### CLI Mode (One-Shot Processing)
Without the `-s` flag, Tuliprox runs as a one-time processor:
```bash
./tuliprox -c config/config.yml -i config/source.yml
```
It loads the configuration, downloads provider lists, applies all filters and mappings, writes the final database/M3U files to the `storage_dir`,
and gracefully exits.
*Useful for: Generating static playlists, debugging mappings, or running via external cronjobs.*
### CLI Arguments Reference
You can view all arguments using `./tuliprox --help`:
| Flag | Purpose |
| :--- | :--- |
| `-H, --home <HOME>` | Sets the home directory (base for config, storage, backup, downloads). Overrides all defaults. |
| `-p, --config-path <DIR>` | Path to the config directory. |
| `-c, --config <FILE>` | Specific path to the `config.yml`. |
| `-i, --source <FILE>` | Specific path to the `source.yml`. |
| `-m, --mapping <FILE>` | Specific path to the mapping file/directory. |
| `-T, --template <FILE>` | Specific path to the template file/directory. |
| `-t, --target <NAME>` | **Target Override:** Forces processing of the specified target *only*. Extremely useful to quickly re-render a broken list via shell without blocking the entire system, even if `enabled: false` in config. |
| `-a, --api-proxy <FILE>` | Specific path to the `api-proxy.yml`. |
| `-s, --server` | Run in continuous Server Mode. |
| `-l, --log-level <LEVEL>` | Override log level (e.g., `info`, `debug`, `trace`). |
| `--genpwd` | Interactively generate a secure `Argon2id` password hash for the `user.txt`. |
| `--healthcheck` | Checks the API over localhost. Returns Exit Code `0` if `{status: "ok"}`. Used by Docker. |
| `--scan-library` | Triggers an incremental scan of local media directories. |
| `--force-library-rescan` | Ignores modification timestamps and forces a full TMDB/PTT re-evaluation of local media. |
| `--dbx`, `--dbm`, `--dbe`, `--dbv`, `--dbms` | Opens internal database viewers (see *Operations & Debugging*). |
---
## 4. Advanced Ecosystem Integration
Tuliprox is built to be a team player. In the `docker/container-templates/` directory of the repository, you will find complete,
production-ready stack templates:
* **Traefik Integration:** Automated Let's Encrypt TLS, strict Content-Security-Policy headers, and ACME DNS-01 challenges.
* **Gluetun (VPN) Integration:** Route your upstream provider requests through WireGuard tunnels to hide your server IP, utilizing SOCKS5 proxy sidecars.
* **CrowdSec Integration:** Protect your Tuliprox instance against L7 AppSec attacks, path traversal, and brute-force attempts using Traefik Bouncers.
*(For an in-depth implementation guide on these templates, see the [Build & Deploy (For Professionals)](build-and-deploy.md) and
[Examples & Recipes](examples-recipes.md) chapters).
-257
View File
@@ -1,257 +0,0 @@
# Mapping And Templates
Tuliprox supports two layers of editorial logic:
- reusable templates
- mapping rules written in a small DSL
## Template loading
Templates can live:
- inline in `source.yml`
- inline in `mapping.yml`
- centrally in files or directories configured via `template_path`
If a directory is used, all `*.yml` files are loaded in alphanumeric order.
Example:
```yaml
templates:
- name: delimiter
value: '[\s_-]*'
- name: quality
value: '(?i)(?P<quality>HD|LQ|4K|UHD)?'
```
Referenced as:
```text
^.*TF1!delimiter!Series?!delimiter!Films?(!delimiter!!quality!)\s*$
```
## `mapping.yml`
Top-level structure:
- `templates`
- `mapping`
Each mapping entry defines:
- `id`
- `match_as_ascii`
- `mapper`
- `counter`
## `match_as_ascii`
If enabled, Tuliprox deunicodes values during matching.
That is useful when filters should match `é`, `ö` or `ß` with simpler ASCII patterns.
## Mapper DSL basics
The DSL supports:
- variable assignment
- string manipulation
- regex matching
- match blocks
- map blocks
- access to playlist fields with `@Field`
- a few builtin functions
Common builtins:
- `concat`
- `uppercase`
- `lowercase`
- `capitalize`
- `trim`
- `print`
- `number`
- `first`
- `template`
- `replace`
- `pad`
- `format`
- `add_favourite`
## Simple example
```yaml
mappings:
mapping:
- id: favourites_news
match_as_ascii: true
mapper:
- filter: 'Group ~ "(?i)news"'
script: |
add_favourite("Favourites")
```
## Match and map blocks
Example `match` block:
```dsl
result = match {
(var1, var2) => result1,
var2 => result2,
_ => default
}
```
Example `map` block:
```dsl
quality = map quality {
"720p" => "HD",
"1080p" => "FHD",
"4K" => "UHD",
_ => quality,
}
```
## Regex captures
Regex matches can expose numbered or named captures:
```dsl
title_match = @Caption ~ "(.*?)\\:\\s*(.*)"
title_prefix = title_match.1
title_name = title_match.2
```
## `for_each`
`for_each` iterates over named results such as split values or regex captures:
```dsl
genres = split(@Genre, "[,/&]")
genres.for_each((_, genre) => {
add_favourite(concat("Genre - ", genre))
})
```
## Counters
Mappings can also define counters:
```yaml
mapping:
- id: simple
counter:
- filter: 'Group ~ ".*FR.*"'
value: 9000
field: title
padding: 2
modifier: suffix
concat: " - "
```
Counter fields:
- `filter`
- `value`
- `field`
- `modifier`
- `concat`
- `padding`
## Grouping example
This groups channels into quality-oriented or category-oriented buckets:
```dsl
group = @Group ~ "(EU|SATELLITE|NATIONAL|NEWS|MUSIC|SPORT|RELIGION|FILM|KIDS|DOCU)"
quality = @Caption ~ "\\b([F]?HD[i]?)\\b"
title_match = @Caption ~ "(.*?)\\:\\s*(.*)"
title_name = title_match.2
quality = map group {
"NEWS" | "NATIONAL" | "SATELLITE" => quality,
_ => null,
}
prefix = map quality {
"HD" => "01.",
"FHD" => "02.",
"HDi" => "03.",
_ => map group {
"NEWS" => "04.",
"DOCU" => "05.",
"SPORT" => "06.",
_ => group
},
}
name = match {
quality => concat(prefix, " FR [", quality, "]"),
group => concat(prefix, " FR [", group, "]"),
_ => prefix
}
@Group = name
@Caption = title_name
```
## Grouping by release year
```dsl
year_text = @Caption ~ "(\\d{4})\\)?$"
year = number(year_text)
year_group = map year {
..2019 => "< 2020",
_ => year_text,
}
@Group = concat("FR | MOVIES ", year_group)
```
## Example `mapping.yml`
```yaml
mappings:
templates:
- name: QUALITY
value: '(?i)\b([FUSL]?HD|SD|4K|1080p|720p|3840p)\b'
mapping:
- id: all_channels
match_as_ascii: true
mapper:
- filter: 'Caption ~ "(?i)^(US|USA|United States).*?TNT"'
script: |
quality = uppercase(@Caption ~ "!QUALITY!")
quality = map quality {
"720p" => "HD",
"1080p" => "FHD",
"4K" => "UHD",
"3840p" => "UHD",
_ => quality,
}
@Group = "United States - Entertainment"
```
## Filter hints
Filters support:
- `NOT`
- `AND`
- `OR`
- regex matches with `~`
- `Type = live|vod|series`
Fields:
- `Group`
- `Title`
- `Name`
- `Caption`
- `Url`
- `Genre`
- `Input`
- `Type`
For Rust regex testing, `regex101.com` works well if the Rust flavor is selected.
+102
View File
@@ -0,0 +1,102 @@
# 🛠️ Operations & Debugging (CLI & DB Dumps)
Tuliprox is designed as a "Fire & Forget" stream broker. However, when streams stutter, provider connection limits block your users, or EPG data and
TMDB covers do not match, the engine provides deep, low-level insights under the hood.
This chapter covers the Command Line Interface (CLI), Logging architecture, and the internal Database Viewers.
## 1. Command Line Arguments (CLI Flags)
While Tuliprox is usually run via Docker, understanding the CLI flags is crucial for debugging and manual interventions.
| Flag | Purpose & Technical Background |
| :--- | :--- |
| `-s, --server` | Starts continuous Server Mode (API, Web UI, Background Workers). Without this flag, Tuliprox acts as a "One-Shot" playlist generator that downloads, processes, and immediately exits. |
| `-H, --home <DIR>` | Sets the Home Directory. All relative paths in the configuration are resolved against this directory. If not set, resolves via `TULIPROX_HOME` env variable, or finally the binary's directory. |
| `-c, -i, -a, -m, -T` | Overrides specific config paths (e.g., `-c /etc/tuliprox/config.yml`). Useful for testing experimental configurations without altering the production setup. |
| `-t, --target <NAME>` | **Targeted Processing:** Forces processing of the specified target *only*. **Crucial:** This bypasses the `enabled: false` state in the config! Extremely useful to quickly re-render a broken list via cron/shell without blocking the entire system with other heavy targets. |
| `--genpwd` | Interactively generates a secure `Argon2id` password hash for the `user.txt` file. Never store plaintext passwords! |
| `--healthcheck` | Docker Support: Pings the API over localhost. Returns Exit Code `0` if the server responds with `{"status": "ok"}`. |
| `--scan-library` | Triggers an incremental scan of the local media directory (if configured). |
| `--force-library-rescan` | Ignores modification timestamps and forces a full TMDB/PTT re-evaluation of all local media files. |
---
## 2. Logging Levels and Module Filtering
Tuliprox utilizes the powerful Rust `env_logger` crate. The log verbosity can be controlled at an extremely granular level via `config.yml`
(`log.log_level`), the environment variable `TULIPROX_LOG`, or the CLI flag `-l`.
The evaluation hierarchy is: **CLI Argument > Env-Var > config.yml > Default (`info`)**.
Available levels: `trace`, `debug`, `info`, `warn`, `error`.
**The Magic of Module Filtering:**
Often, you do not want to set the entire system to `trace` (which would flood your console and disk), but rather investigate a specific algorithm.
You can pass comma-separated module paths:
```bash
# Everything on Info, but the internal Mapper on Trace
# (Useful to make print() commands from the DSL visible!):
./tuliprox -s -l "info,tuliprox::foundation::mapper=trace"
# Show me all low-level HTTP-Connection errors from the Hyper crate:
./tuliprox -s -l "info,hyper_util::client::legacy::connect=error"
```
*Note: If `log.sanitize_sensitive_info` is set to `true` in the config (default), Tuliprox masks passwords, provider URLs, and external client IPs in
the logs with `***`. This is strongly recommended so you can safely share logs on GitHub or Discord!*
---
## 3. Database Dumps (B+Tree Analysis)
Tuliprox is built to be extremely resource-efficient. It does not keep massive playlists (often > 200,000 entries) permanently in RAM. Instead, it
stores all parsed metadata, enriched by FFprobe and TMDB, in highly optimized local **B+Tree Database files** (`.db`).
Sometimes you need to know *exactly* what Tuliprox has discovered in the background about a specific stream. Using the built-in dump flags, you can
output these binary files in clean JSON format to your console (or pipe them into a file).
You must point the flag directly at the corresponding `.db` file inside your `storage_dir` (e.g., `/app/data/`):
| Flag & Example | Usage & Purpose |
| :--- | :--- |
| **`--dbx <PATH>`**<br>`./tuliprox --dbx ./data/input_name/xtream/video.db` | **Xtream DB:** Reads the metadata derived from the Xtream API. Shows you the final JSON payloads with resolved TMDB IDs, extracted video codecs (e.g., H264), and bitrates. |
| **`--dbm <PATH>`**<br>`./tuliprox --dbm ./data/input_name/m3u.db` | **M3U Playlist DB:** Reads the raw M3U entries. Ideal for seeing how the fallback logic for `Tvg-ID` or `Virtual_ID` reacted to messy provider tags. |
| **`--dbe <PATH>`**<br>`./tuliprox --dbe ./data/input_name/xtream/epg.db` | **EPG DB:** Prints the fully matched XMLTV grid. You see a list of all programmes with their correct Unix timestamps. |
| **`--dbms <PATH>`**<br>`./tuliprox --dbms ./data/input_name/metadata_retry_state.db` | **Metadata Retry Status (Cooldowns):** Extremely important! Shows you the asynchronous backoff state. If TMDB finds no info for a stream, it lands in a cooldown here. Shows `attempts: 3`, `last_error: "404 Not Found"`, `cooldown_until_ts: 1740000000`. This explains *why* a movie isn't being updated. |
| **`--dbv <PATH>`**<br>`./tuliprox --dbv ./data/target_name/id_mapping.db` | **Target-ID Mapping:** Tracks the stability of stream UUIDs across updates. Shows which original Provider-ID points to which internal Virtual-ID. |
### Example output via `--dbms`
```json
{
"Stream_ID_4242": {
"resolve": {
"attempts": 3,
"next_allowed_at_ts": 1718000000,
"cooldown_until_ts": 1718604800,
"last_error": "TMDB lookup completed without matching result",
"tmdb":null,
"updated_at_ts":1718604910
}
}
}
```
**Diagnosis:** This dump immediately tells you: Tuliprox tried three times to find the movie on TMDB, failed every time, and has now paused this
movie until `cooldown_until_ts` (e.g., 7 days in the future) to save API traffic and prevent rate-limiting.
---
## 4. Hot Reloading Caveats
Tuliprox supports hot-reloading for specific files (`mapping.yml`, `api-proxy.yml`) if `config_hot_reload: true` is set in `config.yml`.
**Important Note for Docker Bind Mounts:**
If you edit a file on your host system that is bind-mounted into the container (e.g., `nano /home/user/tuliprox/config/mapping.yml`), the file
watcher might report the inotify event using the *original host path* instead of the container's mount point `/app/config/mapping.yml`.
Tuliprox attempts to resolve this, but depending on your host OS (Windows/WSL vs Linux), filesystem events can be flaky. If hot-reload fails to
trigger, a container restart (`docker restart tuliprox`) is the safest fallback.
---
-216
View File
@@ -1,216 +0,0 @@
# Streaming And Proxy
Tuliprox is not only a playlist transformer.
Its runtime streaming behavior is a major part of the project.
## Reverse proxy vs redirect
Tuliprox can either redirect to provider URLs or proxy traffic itself.
Proxy mode gives Tuliprox control over:
- user limits
- provider limits
- custom fallback responses
- HLS and catchup session handling
- stream sharing
## `reverse_proxy.stream`
Important fields:
- `retry`
- `metrics_enabled`
- `buffer`
- `throttle`
- `grace_period_millis`
- `grace_period_timeout_secs`
- `grace_period_hold_stream`
- `shared_burst_buffer_mb`
- `hls_session_ttl_secs`
- `catchup_session_ttl_secs`
### `retry`
If `true`, Tuliprox retries provider streams when the upstream disconnects unexpectedly.
### `metrics_enabled`
If `true`, Tuliprox samples active stream throughput and transferred bytes and exposes them to the Web UI streams table.
Use this when you want live per-stream bandwidth visibility while debugging or operating the proxy.
Notes:
- default is `false`
- metrics are only collected for reverse-proxied streams
- enabling it adds lightweight runtime accounting for active streams
- it does not change playlist output or stream selection behavior
### `buffer`
`buffer` has:
- `enabled`
- `size`
`size` is the number of 8192-byte chunks.
`1024` is roughly `8 MB`.
When `share_live_streams` is enabled, each shared live channel keeps at least the shared burst buffer in memory.
### `throttle`
Bandwidth throttling supports units such as:
- `KB/s`
- `MB/s`
- `KiB/s`
- `MiB/s`
- `kbps`
- `mbps`
- `Mibps`
### Grace period
`grace_period_millis` and `grace_period_timeout_secs` protect users during rapid reconnects, seeks and channel switches.
`grace_period_hold_stream` decides whether Tuliprox waits for the grace decision before it starts sending media data.
## HLS and catchup session reservation
HLS and catchup behave differently from plain TS streaming because clients repeatedly connect, fetch data, disconnect and reconnect.
Tuliprox therefore keeps a short-lived provider-account reservation rather than holding a real provider slot open the entire time.
### `hls_session_ttl_secs`
For HLS:
- the real provider slot is held only during the active playlist or segment request
- between requests, Tuliprox keeps only an account reservation
- the same client/session tries to reuse the same provider account
- channel switches from the same client can take over the reservation immediately
### `catchup_session_ttl_secs`
Catchup has the same account-affinity problem, especially during seeks and reconnects.
Tuliprox therefore applies the same reservation model to catchup.
Important distinction:
- normal TS streaming does not use this family reservation model
- HLS and catchup do
## Shared live streams
When enabled, multiple users can attach to the same upstream live stream instead of opening separate provider connections.
That reduces provider pressure but also means stream priority has to be managed at the shared-stream level.
Current behavior:
- the first viewer starts a shared stream immediately
- additional viewers on the same channel join the existing shared stream
- the effective priority of the shared stream is always the highest priority of its remaining viewers
- when a viewer leaves, the shared stream priority is recalculated
- if provider capacity is full and a higher-priority user starts another stream, the lower-priority shared stream can be preempted
- equal priority does not preempt a different running stream
Example:
```yaml
reverse_proxy:
stream:
retry: true
metrics_enabled: true
```
## Priority and preemption
Tuliprox can prioritize users and internal tasks differently.
Higher-priority user streams can displace lower-priority traffic when provider capacity is exhausted.
The important design rule is:
- user playback wins over low-priority internal work
Priority uses a nice-style scale:
- lower number = higher priority
- negative values are allowed
- equal priority does not preempt a different stream
Probe tasks use `metadata_update.probe.user_priority`.
If preempted by a higher-priority user, they are cancelled immediately and release provider capacity right away.
## Custom stream responses
When Tuliprox cannot serve the real stream, it can return custom fallback videos for cases such as:
- user connection exhausted
- provider connection exhausted
- channel unavailable
- low-priority stream preempted
That makes failure modes easier to understand for end users and downstream clients.
The fallback files are discovered by filename in `custom_stream_response_path`.
## Other reverse-proxy sections
### `cache`
LRU cache for proxied resources such as logos.
If `resource_rewrite_disabled` is set to `true`, the cache is effectively disabled because Tuliprox can no longer rewrite and track resource URLs safely.
### `resource_rewrite_disabled`
Disable rewritten resource URLs when Tuliprox runs behind another proxy and you do not want Tuliprox-generated asset URLs.
### `rate_limit`
Per-IP rate limiting with:
- `enabled`
- `period_millis`
- `burst_size`
### `disabled_header`
Controls which request headers are stripped before upstream requests:
- `referer_header`
- `x_header`
- `cloudflare_header`
- `custom_header`
### `resource_retry`
Controls retries for proxied upstream resources:
- `max_attempts`
- `backoff_millis`
- `backoff_multiplier`
### `geoip`
Optional country lookup from CSV IP ranges.
### `rewrite_secret`
Persistent secret used for generating and validating rewritten resource URLs.
Set it explicitly if rewritten URLs should survive restarts unchanged.
## VLC seek problem and grace tuning
Seeking often produces very fast reconnects and partial range requests.
If the previous upstream connection is not fully closed yet, the provider can still count it against max-connections.
The usual mitigation is:
```yaml
reverse_proxy:
stream:
grace_period_millis: 2000
grace_period_timeout_secs: 5
```
That allows a short overlap, then re-checks whether stale connections have disappeared before enforcing the limit.
+40
View File
@@ -0,0 +1,40 @@
# 🛠️ Troubleshooting & Resilience
Running a reverse proxy for IPTV involves navigating the quirks of various video players and the strict limitations of upstream providers. This page
documents common "real-world" behaviors that can lead to stream interruptions or provider bans, and how to mitigate them using Tuliprox's resilience
features.
The solutions below will help you fine-tune your configuration for a seamless experience.
---
## 1. The VLC "Seek" Problem (Grace Periods)
**The Problem:** A user watches a VOD movie via reverse proxy. They press "Fast forward 10 seconds" in VLC. VLC calculates the new byte offset, kills
the TCP connection, and instantly fires a new HTTP GET request (with a `Range` header) to your Tuliprox server.
Tuliprox opens a new connection to the upstream provider. However, since the old connection takes milliseconds to officially close at the provider
side, the provider sees **two** active streams. If you only paid for 1 connection, the provider throws a 509 Bandwidth Exceeded error or bans your IP!
**The Solution in `config.yml`:**
```yaml
reverse_proxy:
stream:
grace_period_hold_stream: true
grace_period_millis: 2000
grace_period_timeout_secs: 5
```
**What happens now?**
Tuliprox detects the bottleneck and grants a temporary "grace" state:
* **Hold State:** Because `grace_period_hold_stream: true` is set, Tuliprox keeps the client connection "warm" but
waits before requesting new bytes from the provider.
* **The Handover:** It waits for `grace_period_millis` (2000ms) to give the provider's server time to register the old connection as closed.
* **Resolution:**
* **Success:** If the old "ghost" connection dies within the window ➔ The new stream flows instantly.
* **Timeout:** If the old connection persists beyond `grace_period_timeout_secs` (5s) ➔ Grace is revoked, and the client receives the
`user_connections_exhausted.ts` video.
---