mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-27 17:22:39 +01:00
New Features
Added separate processing and persistence filters, with empty/non-empty checks and expanded filter fields.
Added persistence-stage filter controls in target settings.
Added clear_invalid_epg_ids, preserving playlist entries while clearing unresolved EPG IDs; the legacy setting remains supported.
Added safer, atomic source configuration updates that preserve comments and formatting.
Bug Fixes
Null-like Xtream and Stalker values are now handled correctly.
Duplicate credential warnings provide clearer details.
284 lines
10 KiB
Markdown
284 lines
10 KiB
Markdown
# 🧪 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 Provider Setup
|
|
|
|
To use Tuliprox, you need to configure at least three core files:
|
|
|
|
1. `config.yml`: Defines the core server settings.
|
|
2. `source.yml`: Defines the upstream provider and your output targets.
|
|
3. `api-proxy.yml`: Defines your local/external server addresses and the users who can access the targets.
|
|
|
|
### Scenario: Minimal Xtream Setup
|
|
|
|
You have an Xtream provider (`http://fantastic.provider.xyz:8080`) and want to simply "pass through" the streams while utilizing the Web UI.
|
|
|
|
**1. `config.yml` (Core Engine):**
|
|
|
|
```yaml
|
|
api:
|
|
host: 0.0.0.0
|
|
port: 8901
|
|
web_root: ./web
|
|
storage_dir: ./data
|
|
update_on_boot: true
|
|
```
|
|
|
|
*This configuration starts Tuliprox on port 8901. Downloaded playlists are stored inside the `data` folder.*
|
|
*Setting `update_on_boot: true` is helpful during initial setup to immediately pull provider data.*
|
|
|
|
**2. `source.yml` (Inputs & Targets):**
|
|
|
|
```yaml
|
|
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
|
|
```
|
|
|
|
*Here we define the input source based on the provider's information and create a 1:1 mapped target (`clean_list`).
|
|
The optional target filter is omitted, so every entry passes the processing stage.*
|
|
|
|
**3. `api-proxy.yml` (Servers & Users):**
|
|
|
|
```yaml
|
|
server:
|
|
- name: default
|
|
protocol: http
|
|
host: 192.168.1.41
|
|
port: 8901
|
|
timezone: Europe/Berlin
|
|
message: Welcome to tuliprox
|
|
- name: external
|
|
protocol: https
|
|
host: tv.my-domain.com
|
|
port: 443
|
|
timezone: Europe/Berlin
|
|
path: iptv
|
|
|
|
user:
|
|
- target: clean_list
|
|
credentials:
|
|
- username: me
|
|
password: mypass
|
|
proxy: redirect # Tuliprox only redirects the player; it does not proxy the video stream
|
|
server: default
|
|
```
|
|
|
|
*We define two server endpoints (internal LAN and external HTTPS). We then bind a user to the `clean_list` target.*
|
|
*Using `proxy: redirect` saves bandwidth on your server during initial testing.*
|
|
|
|
**Resulting Client Endpoints:**
|
|
You can now enter the following credentials into your IPTV player (e.g., TiviMate, IPTV Smarters):
|
|
|
|
* **Portal URL:** `http://192.168.1.41:8901`
|
|
* **Username:** `me`
|
|
* **Password:** `mypass`
|
|
|
|
Start `tuliprox`, fire up your IPTV-Application, enter credentials and watch.
|
|
|
|
### Scenario: Minimal M3U Setup
|
|
|
|
If your provider uses standard M3U instead of the Xtream Codes API, simply adjust the input type in `source.yml`.
|
|
Tuliprox automatically extracts credentials from the URL if present.
|
|
|
|
**`source.yml` (M3U Variation):**
|
|
|
|
```yaml
|
|
inputs:
|
|
- type: m3u
|
|
name: my_m3u_provider
|
|
url: "http://fantastic.provider.xyz:8080/get.php?username=tvjunkie&password=junkie.secret&type=m3u_plus&output=ts"
|
|
cache_duration: 1d
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Advanced Filtering (Exclusion Logic & Templates)
|
|
|
|
A 1:1 pass-through is rarely what you want. Upstream provider lists are often cluttered with countries or VOD sections you don't need.
|
|
|
|
Tuliprox uses a custom DSL (Domain Specific Language) for filtering. You need a basic understanding of Regular Expressions (Regex).
|
|
A good site for learning and testing is[regex101.com](https://regex101.com/) (ensure you select the **Rust** flavor).
|
|
|
|
### The Basics
|
|
|
|
DSL filters are applied in `source.yml` at the `target` level. The tilde `~` operator executes a Regex match.
|
|
Evaluatable fields include `Group`, `Title`, `Name`, `Url`, and `Type`.
|
|
|
|
* **Simple Inclusion:**
|
|
Include all categories: `Group ~ ".*"`
|
|
* **Case-Insensitive Inclusion:**
|
|
Include all categories whose name contains "shopping": `Group ~ "(?i).*Shopping.*"`
|
|
* **Reversing Logic (Exclusion):**
|
|
I don't want any shopping categories: `NOT (Group ~ "(?i).*Shopping.*")`
|
|
* **Specific Channel Exclusion:**
|
|
Filtering a specific channel, even if it appears in multiple groups:
|
|
`NOT (Title ~ "FR: TV5Monde" AND Group ~ "FR: TF1")`
|
|
|
|
### Complex Matrix (Bracket Logic)
|
|
|
|
Allow everything from Germany (DE) and France (FR), except Series and Commercials. Always allow Australia (AU).
|
|
|
|
```text
|
|
((Group ~ "^(DE|FR).*") AND NOT (Group ~ "(?i).*SERIES.*" OR Group ~ "(?i).*COMMERCIAL.*")) OR (Group ~ "^AU.*")
|
|
```
|
|
|
|
### Refactoring with Templates (The DRY Principle)
|
|
|
|
As you can see, filter strings can quickly become massive and unmaintainable.
|
|
This is where **Templates**[**Templates**](./configuration/template.md) come into play.
|
|
You can disassemble a monolithic filter into smaller, readable parts and combine them.
|
|
|
|
```yaml
|
|
templates:
|
|
- name: NO_SHOPPING
|
|
value: 'NOT (Group ~ "(?i).*Shopping.*" OR Group ~ "(?i).*Einkaufen.*" OR Group ~ "(?i).*téléachat.*")'
|
|
- name: GERMAN_CHANNELS
|
|
value: 'Group ~ "^DE: .*"'
|
|
- name: FRENCH_CHANNELS
|
|
value: 'Group ~ "^FR: .*"'
|
|
- name: NO_TV5MONDE_IN_TF1
|
|
value: 'NOT (Group ~ "FR: TF1" AND Title ~ "FR: TV5Monde")'
|
|
- name: EXCLUDED_CHANNELS
|
|
value: '!NO_TV5MONDE_IN_TF1! AND !NO_SHOPPING!'
|
|
|
|
# Final combination
|
|
- name: MY_CHANNELS
|
|
value: '!EXCLUDED_CHANNELS! AND (!GERMAN_CHANNELS! OR !FRENCH_CHANNELS!)'
|
|
|
|
inputs:
|
|
- type: xtream
|
|
name: my_provider
|
|
url: http://fantastic.provider.xyz:8080
|
|
|
|
sources:
|
|
- inputs:
|
|
- my_provider
|
|
targets:
|
|
- name: curated_list
|
|
output:
|
|
- type: xtream
|
|
filter: "!MY_CHANNELS!"
|
|
```
|
|
|
|
*The resulting playlist now cleanly contains all French and German channels, minus any shopping channels or specifically blacklisted streams.*
|
|
|
|
---
|
|
|
|
## 3. Generating Custom Fallback Videos (FFmpeg)
|
|
|
|
If a provider stream returns a 404 or the user hits their connection 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 Ingress):** 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 e.g. to Mullvad or Proton VPN.
|
|
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.
|
|
4. **IPTV-org-epg (EPG guide):** Generates a local XMLTV guide for the selected channels and makes it available to
|
|
Tuliprox.
|
|
|
|
### 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/.env.wg`, 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://gluetun: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.
|
|
|
|
>**⚠️ Important:** make sure you add
|
|
>[tuliprox-cover-whitelist.yaml](../../docker/container-templates/crowdsec/crowdsec/config/parsers/s02-enrich/tuliprox-cover-whitelist.yaml)
|
|
> to `crowdsec/config/parsers/s02-enrich/tuliprox-cover-whitelist.yaml` in your Crowdsec instance.
|
|
> Otherwise, you could quickly end up banning yourself.
|
|
|
|
**5. Configure IPTV-org-epg:**
|
|
Copy the desired channel entries from the IPTV-org `sites` files to
|
|
`container-templates/iptv-org/data/channels.xml`. For every channel, `xmltv_id` must exactly match the
|
|
corresponding `@epg_channel_id` in Tuliprox. For example, `xmltv_id="BBCOne.uk@LondonHD"` matches
|
|
`@epg_channel_id = "BBCOne.uk@LondonHD"`. If necessary, adjust `xmltv_id` in `channels.xml` or set the corresponding
|
|
`@epg_channel_id` in `mapping.yml`.
|
|
|
|
Start it: `docker-compose up -d`. Add the generated guide to the corresponding input in your `source.yml`:
|
|
|
|
```yaml
|
|
epg:
|
|
sources:
|
|
- url: http://iptv-org-epg:3000/guide.xml
|
|
```
|