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).
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
interner_gc_interval_secs: 180
interner_gc_min_pool_size: 100
user_access_control: false
custom_stream_response_path: null
custom_stream_response_timeout_secs: 0
custom_stream_response_enabled: true # default: true (serve the fallback videos)
custom_stream_response_error_status: 502 # 4xx/5xx only; returned when custom_stream_response_enabled is false
api:
web_ui:
log:
schedules:
messaging:
video:
proxy:
ipcheck:
hdhomerun:
library:
reverse_proxy:
metadata_update:| 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. If you process the same provider multiple times, each thread uses a connection. Keep in mind that you might hit the provider's max-connections limit! |
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. Be aware that different configurations (e.g. user bouquets) alongside the playlists are stored in this 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 and merges them. Note: This is a lexicographic sort, meaning m_10.yml comes before m_2.yml. Name files carefully (e.g., m_01.yml, m_02.yml). |
template_path |
String | No | template.yml |
Path to the template macro file. Specifying a folder here is also possible and highly recommended (see above). |
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 |
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. (See Bind-Mount Note below) |
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 and the connection attempt continues until the provider closes it or a network timeout occurs (Warning: risk of hanging threads!). |
interner_gc_interval_secs |
Int | No | 180 |
Interval in seconds between background string interner garbage-collection checks. Lower values reclaim unused interned strings sooner, but run the GC gate more often. Higher values reduce GC overhead, but can retain more memory for longer. Must be greater than 0. |
interner_gc_min_pool_size |
Int | No | 100 |
Minimum number of interned strings required before the background interner GC runs. This avoids paying the interner write-lock cost when the pool is very small. Lower values favor aggressive cleanup; higher values favor throughput and lower lock contention at the cost of extra retained memory. |
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. See section Custom Stream Response for exact filenames. |
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. |
custom_stream_response_enabled |
Bool | No | true |
Enables configured custom MPEG-TS fallback videos for stream errors. When false, factories skip the video body and call sites return custom_stream_response_error_status; useful behind reverse proxies that intercept 4xx/5xx responses instead of keeping fallback sockets open. |
custom_stream_response_error_status |
Int | No | 502 |
HTTP status code returned when custom_stream_response_enabled is false. Must be a 4xx or 5xx code (the prepare() step rejects anything outside that range; 0 is silently clamped to 502). Common choices: 404 (channel not found), 502 (bad gateway — upstream failed), 503 (service unavailable — overloaded). |
If you use Bind-Mounts (e.g., in fstab or Docker), the filesystem watcher may report the original source path
instead of your mount point.
- Example Setup:
/home/tuliprox/config(Source) →/config(Mount Point). - Behavior: If you configure Tuliprox to watch
/config, the watcher might still trigger events using the path/home/tuliprox/config. - Solution: Ensure your internal Tuliprox paths match the paths reported by the OS kernel to ensure the hot-reload trigger fires correctly.
| Block | Description | Link |
|---|---|---|
api |
Internal web server binding settings. | See section |
web_ui |
Web Dashboard, RBAC, and Authentication. | See section |
log |
Console output verbosity and sanitization. | See section |
schedules |
Automated background tasks (Cronjobs). | See section |
messaging |
Webhooks & Push-Notifications (Telegram, Discord, etc.). | See section |
video |
Extension mapping and Web UI download behavior. | See section |
proxy |
SOCKS5/HTTP proxy settings for outgoing requests. | See section |
ipcheck |
IP detection to verify in the Web UI which public IP Tuliprox is currently using. | See section |
hdhomerun |
Virtual DVB-C/T network tuner emulation. | See section |
library |
Local Media Library integration. | See Local Library |
reverse_proxy |
Streaming buffers, rate limits, caching. | See Reverse Proxy |
metadata_update |
TMDB matching, FFprobe processing, Job Queues. | See Metadata Update |
(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.)
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.
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. |
Tuliprox ships with a comprehensive Web Dashboard containing a Web Player, Playlist Editor, User Management, and Live Logs.
web_ui:
enabled: true
user_ui_enabled: true
path: admin
player_server: default
kick_secs: 90
combine_views_stats_streams: false
landing_page: dashboard
stream_info:
hide_group: false
hide_ip: false
hide_country: false
hide_shared: false
hide_duration: false
hide_bandwidth: false
hide_transferred: false
hide_player: false
hide_user_comment: false
hide_epg: false
content_security_policy:
enabled: true
custom-attributes:
- "style-src 'self' 'nonce-{nonce_b64}'"
- "img-src 'self' data:"
auth:
enabled: true
issuer: tuliprox
secret: "YOUR_SECRET_JWT_KEY_HERE"
token_ttl_mins: 30
userfile: user.txt
groupfile: groups.txt| 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 favorites/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. |
landing_page |
String | dashboard |
Set the initial landing page for the webui. Possible values are dashboard, stats, streams, stream_history, downloads, users, config, source_editor, playlist_update, playlist_settings, playlist_explorer, playlist_epg, rbac |
stream_info |
Object | null |
Optional visibility controls for fields shown in the active stream display. If omitted, or if all nested hide_* flags are false, the default dashboard view is unchanged. |
Use this optional block to hide selected fields in the active stream display shown in the dashboard.
web_ui:
stream_info:
hide_group: true
hide_ip: true
hide_epg: true| Parameter | Type | Default | Technical Impact & Background |
|---|---|---|---|
hide_group |
Bool | false |
Hides the stream group/category label. |
hide_ip |
Bool | false |
Hides the client IP shown for an active stream. |
hide_country |
Bool | false |
Hides the detected client country flag/code. |
hide_shared |
Bool | false |
Hides the shared-stream indicator. |
hide_duration |
Bool | false |
Hides the running playback duration. |
hide_bandwidth |
Bool | false |
Hides the live bandwidth badge when metrics are available. |
hide_transferred |
Bool | false |
Hides the transferred-bytes badge when metrics are available. |
hide_player |
Bool | false |
Hides the player / user-agent field. |
hide_user_comment |
Bool | false |
Hides the user comment next to the active stream title. |
hide_epg |
Bool | false |
Hides per-stream EPG information and skips the associated dashboard EPG fetch/update work for that display. |
- If every
hide_*flag isfalse, Tuliprox treats the wholestream_infoblock as empty/default. - This setting is global for the Web UI. It is not currently per-user.
This block enhances security by restricting which resources the browser is allowed to load.
- Default Directives: When
enabled: true, Tuliprox automatically applies:default-src 'self'script-src 'self' 'wasm-unsafe-eval' 'nonce-{nonce_b64}'frame-ancestors 'none'
- Customization: Use
custom-attributesto add specific rules (e.g., allowing external channel logos viaimg-src).
Tuliprox features a robust Role-Based Access Control (RBAC) system.
| Parameter | Type | Default | Technical Impact & Background |
|---|---|---|---|
enabled |
Bool | true |
Master switch for UI authentication. Disabling this exposes the dashboard to anyone with network access. |
issuer |
String | tuliprox |
The identifier for the JWT "iss" field. |
secret |
String | (Random) |
Critical for JWT encryption. Use a static 64-character hex string using Node.js (see Secret Generation) to keep sessions valid across restarts. If omitted, Tuliprox generates one in-memory, but all active logins will invalidate on every server restart! |
token_ttl_mins |
Int | 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 | user.txt |
The file storing Admins and Web Users. |
groupfile |
String | groups.txt |
The RBAC (Role-Based Access Control) definition file. |
- File Resolution: If
userfileis not defined with an absolute path, Tuliprox automatically looks for it within your globalconfig_dir. Ensure the process has sufficient read permissions for this directory. - RBAC (Role-Based Access Control): This system manages Web UI access levels (e.g., Admins vs. Bouquet-Editors)
by assigning users to specific permission groups defined in
groups.txt.
This file stores users, Argon2 password hashes, and RBAC groups. Generate secure passwords via CLI:
./tuliprox --genpwd.
The userfile has the following format per line: username:argon2_hash[:group1,group2]
Example:
# 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
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,...
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, download.read/write. Note: Write does not imply Read. A group must explicitly
grant both if users need
to view
and edit content.
To ensure security, Tuliprox does not store plain-text passwords. You must generate an encrypted hash using the built-in generator:
Local Installation:
./tuliprox --genpwdDocker Installation:
docker container exec -it tuliprox ./tuliprox --genpwdAfter running the command, copy the generated Argon2id string and manually paste it into your userfile next to the
desired username.
As mentioned in the table above, a static secret is required to keep sessions valid across restarts.
You can generate a secure 32-byte (64-character hex) key using Node.js:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"- File Location: If Tuliprox cannot find the file, check your
storage_diror provide an absolute path in theuserfileparameter. - Permissions: Ensure the Tuliprox process has read access to the
userfileandgroupfile. - Session Invalidated: If you change the
secretinconfig.yml, all currently logged-in users will be forced to log in again.
Controls console output verbosity and sanitization.
log:
sanitize_sensitive_info: true
log_active_user: false
log_level: info
runtime_config_report_enabled: false
runtime_config_report_format: yaml| 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. |
runtime_config_report_enabled |
Bool | false |
Opt-in startup dump of the complete effective runtime configuration after defaults, path resolution, and runtime preparation have been applied. Sensitive values such as passwords, secrets, tokens, and API keys are masked as ***. |
runtime_config_report_format |
String | yaml |
Output format for the runtime config report. Supported values: json and yaml. Both formats are emitted as pretty, multi-line output. Ignored unless runtime_config_report_enabled is true. |
If enabled, Tuliprox keeps the existing startup summary and prints the additional full runtime report once during startup. The report includes:
- prepared
config.yml - prepared
source.yml - loaded mappings/templates/api-proxy sections when present
- resolved runtime
paths
The report is intended for diagnostics and verification of effective defaults. Sensitive values are redacted before logging.
Automate your background updates to keep your playlists, library, and Geo-IP data synchronized.
⚠️ Provider Safety Warning: Do not schedule updates to run every second or minute. Excessive requests can lead to your IP being banned by your provider. Updating twice a day is generally sufficient for most use cases.
Tuliprox uses a standard cron syntax but strictly requires 7 fields, starting with seconds:
# ┌──────────────────────────── second (0 - 59)
# │ ┌──────────────────────── minute (0 - 59)
# │ │ ┌──────────────────── hour (0 - 23)
# │ │ │ ┌─────────────── day of month (1 - 31)
# │ │ │ │ ┌─────────── month (1 - 12)
# │ │ │ │ │ ┌─────── day of week (0 - 6) (Sunday to Saturday; 7 is also Sunday)
# │ │ │ │ │ │ ┌─── year (optional, e.g., 2026)
# │ │ │ │ │ │ │
# sec min hour dom mon dow year
0 0 8 * * * *In current versions, schedules are defined as a list of tasks.
schedules:
# Every morning at 08:00:00 (Playlist Update for specific targets)
- schedule: "0 0 8 * * * *"
type: PlaylistUpdate
targets: [ "m3u_target", "xtream_target" ]
# Every evening at 20:00:00 (Full Library Scan)
- schedule: "0 0 20 * * * *"
type: LibraryScan
# Every Monday at 04:00:00 (Geo-IP Database Refresh)
- schedule: "0 0 4 * * 1 *"
type: GeoIpUpdate
# Every 1st of the month at 04:00:00
- schedule: "0 0 4 1 * * *"
type: GeoIpUpdate| Parameter | Type | Default | Description |
|---|---|---|---|
schedule |
String | - | Cron expression with 7 fields (Seconds included at the start). |
type |
Enum | PlaylistUpdate |
The task to execute. See Task Types below. |
targets |
List | - | (Optional, only for PlaylistUpdate) List of target names to restrict the update to. If omitted, all enabled targets are updated. |
PlaylistUpdate: Triggers the processing pipeline for your target playlists. It downloads provider data, applies filters/maps, and updates the local databases.LibraryScan: Initiates a scan of the local media library. Note: This requires thelibraryconfiguration to be enabled.GeoIpUpdate: Downloads the latest MaxMind/Geo-IP database and rebuilds the internal binary file. Note: Requiresreverse_proxy.geoip.enabled: true.
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.
Messaging is strictly opt-in. You must explicitly define which event types should trigger a notification using the
notify_on list.
Available Event Types:
info: General operational information.stats: Summary of processed items and performance metrics after a run.error: Alerts when processing or source fetching fails.watch: Triggered by changes in monitored groups/targets.
messaging:
notify_on: [ "info", "stats", "error", "watch" ]
# Telegram: Supports Markdown and Group Topics
telegram:
markdown: true
bot_token: "<TOKEN>"
chat_ids:
- "<CHAT_ID>"
- "<CHAT_ID>:<MESSAGE_THREAD_ID>" # Use colon to target specific Discord-like topics/threads
templates:
stats: 'file:///config/messaging_templates/telegram_stats.templ'
# Discord: Webhook integration
discord:
url: "<WEBHOOK_URL>"
templates:
info: '{"content": "🚀 Tuliprox Info: {{message}}"}'
# Pushover: Simple mobile push alerts
pushover:
token: "<API_TOKEN>"
user: "<USER_KEY>"
# url: "https://api.pushover.net/1/messages.json" # Optional default
# REST: Generic Webhook/API support
rest:
url: "https://my-api.local/alert"
method: "POST" # Optional, defaults to POST
headers:
- "Content-Type: application/json"
templates:
error: '{"text": "Alert: {{message}}", "type": "{{kind}}"}'For Telegram, Discord, and REST, Tuliprox uses Handlebars to format message bodies. This allows for rich, structured notifications (e.g., Discord Embeds or Markdown tables).
- Raw String: Define the template directly in your
config.yaml(best for simple one-liners). - URI: Reference a local file (
file://...) or a remote resource (http://...).
Note: If you save your configuration via the Web UI, raw template strings are automatically moved to individual files in
/config/messaging_templates/to keep your main configuration clean.
The Handlebars engine provides a rich context object. Depending on the kind of notification, different variables are
populated:
{{message}}: The primary text payload. Used for human-readableinfomessages or the summary of anerror.{{kind}}: The event category (info,stats,error,watch). Use this in Handlebars helpers (e.g.,{{#if (eq kind "error")}}) to create conditional layouts.{{timestamp}}: The event occurrence time in UTC (ISO 8601 / RFC3339 format).{{stats}}: Execution Metrics & Performance. A comprehensive list of statistics for the last update cycle, covering both ingestion and generation phases.- Structure: A nested list containing
inputs(Source-level metrics) andtargets(Output-level metrics). - Access: Iterate over the main list to access individual source or target reports. Use nested loops for
detailed input/target breakdowns:
{{#each stats}} {{#each inputs}} ... {{/each}} {{/each}}. - Key Properties:
- Metadata: Access
name,type, andtook(execution duration) for each entry. - Error Tracking:
errorsprovides a count of failed items or connection issues during that specific phase. - Filtering Delta: Compare
rawcounts (total items received from the provider) vs.processedcounts ( items that survived your Mapping DSL and filters) to monitor your "Red Thread" efficiency.
- Metadata: Access
- Structure: A nested list containing
{{processing}}: Engine State & Telemetry. Provides insight into the internal execution environment during the task. It includes data on memory allocation peaks, active worker threads, and non-blocking diagnostic warnings.- Access: Access properties directly via dot-notation (e.g.,
{{processing.memory_peak_mb}}). Use this to monitor system health and resource consumption during heavy mapping cycles.
- Access: Access properties directly via dot-notation (e.g.,
{{watch}}: Change Tracking Data. Specifically available for thewatchevent kind. It contains a diff-style object showing exactly which groups or channels were added, removed, or modified compared to the previous state.- Access: Iterate over the change sets using loops. Common keys include
added,removed, andmodified. - Example: Use
{{#each watch.added}} • {{name}} {{/each}}to list all new channels detected in the monitored groups.
- Access: Iterate over the change sets using loops. Common keys include
Telegram (Markdown Report):
Discord (Complex Embed):
Tuliprox monitors the free space on the current working directory's mount and notifies you via the
standard messaging channels (Telegram, Discord, Pushover, REST) before the disk fills up. The feature is
opt-in: no disk_alert block means no monitoring and no notifications.
How it works: A background loop in backend/src/api/sys_usage.rs ticks every 2 seconds (fixed
sampling interval — see SYSTEM_USAGE_INTERVAL in that file, not configurable) and compares the
current working directory's percent-used against two thresholds. The DiskAlertMonitor state machine
(backend/src/api/sys_usage.rs::DiskAlertMonitor) decides when to emit a DiskAlert:
| Level | Default Trigger | Operator Action |
|---|---|---|
Normal |
< warn_percent |
All good. |
Warn |
≥ warn_percent (default 80.0) |
Plan a cleanup. |
Critical |
≥ critical_percent (default 95.0) |
Cleanup is urgent; stream generation may fail. |
A DiskAlert is emitted whenever the level is non-Normal and either:
- the level just changed (Normal → Warn, Warn → Critical, or any step back down), or
- the disk has stayed in the same alert state for at least
repeat_interval_secsseconds since the previous notification.
This periodic re-notification is intentional: a long-running full-disk situation that nobody fixes
would otherwise go silent after the first transition, defeating the purpose of the feature. With the
default repeat_interval_secs: 3600 (1 hour), a stuck-at-87% disk produces one Warn notification per
hour instead of a single one-shot alert.
messaging:
notify_on: [ "info", "stats", "error", "watch" ]
# Disk-space alerts are sent through the channels above. Configure the
# thresholds and the re-arm interval here. The 2-second sampling tick is
# internal and not configurable.
disk_alert:
# Percent-used at which the Warn level is reached. Default: 80.0.
# Must be in [0, 100].
warn_percent: 80.0
# Percent-used at which the Critical level is reached. Default: 95.0.
# Must be in (warn_percent, 100].
critical_percent: 95.0
# Re-arm interval in seconds. While the disk stays in the same alert
# state, the alert is re-sent after this many seconds. Default: 3600
# (1 hour). This is NOT the sampling interval — sampling is fixed at
# 2 seconds and is not currently configurable.
repeat_interval_secs: 3600
telegram:
bot_token: "<TOKEN>"
chat_ids: [ "<CHAT_ID>" ]
templates:
# Custom templates per level. Plain-text defaults are used if omitted.
disk_alert_warn: "⚠️ Disk is {{percent}}% full (Warn threshold)."
disk_alert_critical: "🚨 Disk is {{percent}}% full — cleanup now!"
disk_alert_normal: "✅ Disk back to normal at {{percent}}%."Note on
mounts: Earlier drafts of this section described amountsconfig field. The current implementation has no such field — the disk probe is hard-coded to sample the current working directory's mount (seeDiskProbe::for_cwd()inbackend/src/api/sys_usage.rs). If you need to monitor a different path, change the working directory of the Tuliprox process.
Available Handlebars Variables (all disk_alert_* templates):
{{level}}— The new level after the transition (Warn,Critical, orNormal).{{percent}}— Current percent-used, rounded to one decimal place.{{free_bytes}}/{{total_bytes}}/{{used_bytes}}— Raw byte counts for the sampled mount.{{timestamp}}— Event time in UTC (ISO 8601 / RFC3339).
Available template keys per channel (Handlebars): disk_alert_warn, disk_alert_critical,
disk_alert_normal. If a template is not configured for a level, Tuliprox falls back to a clear plain-text
default line, e.g.:
[WARN] Disk is 82.3% full (free=3.4GB total=19.0GB).Note: Disk alerts are sent through every configured messaging channel. If you want a channel to ignore disk alerts, simply do not declare the matchingdisk_alert_*template key for it and it will still receive the plain-text default. Use the standardnotify_onlist to control which event kinds are sent at all.
Optional video-related behaviors, mostly utilized by the Web UI.
video:
web_search: "https://www.imdb.com/search/title/?title={}"
extensions: [ "mkv", "mp4", "avi", "ts", "webm" ]
download:
directory: /tmp/tuliprox_downloads
headers:
User-Agent: "AppleTV/tvOS/9.1.1"
Accept: "video/*"
organize_into_directories: true
episode_pattern: '.*(?P<episode>[Ss]\d{1,2}(.*?)[Ee]\d{1,2}).*'web_search: A template URL used in the Web UI to quickly search for a movie title (replaces{}with the title).extensions: Defines which file endings Tuliprox categorizes as VOD/Video content when transforming M3U to Xtream.download: Configuration for the Web UI download and recording manager.directory: Where downloaded files and recordings are saved.headers(optional): Custom HTTP headers used for the download request. This is useful for bypassing basic user-agent filters or setting specific media types.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 same base-show folder.download_priority: Default provider priority for VOD/series/episode downloads. Lower values mean higher priority.recording_priority: Default provider priority for live recordings. Lower values mean higher priority.reserve_slots_for_users: Keeps provider headroom for normal foreground users before background-priority transfers may consume the last slots.max_background_per_provider: Limits how many background-priority transfers may run in parallel against one provider.retry_backoff_initial_secs: Initial retry delay for transient download/recording failures.retry_backoff_multiplier: Growth factor applied to each later retry delay.retry_backoff_max_secs: Maximum retry delay once the backoff curve reaches its cap.retry_backoff_jitter_percent: Randomizes retry delays to avoid retry spikes after shared upstream problems.retry_max_attempts: Maximum number of transient retries before a transfer is marked as failed.
Tuliprox handles these transfers like provider-bound background streams:
- They respect provider limits, user priorities, and connection preemption instead of bypassing normal stream capacity.
- Waiting for provider capacity is notify-based, not polling-based.
- The Web UI loads an initial transfer snapshot and then stays synchronized through websocket updates.
- Changes to
video.downloadparticipate in hot config reloads. The background scheduler restarts and active transfers are
re-queued so they continue under the updated download configuration. - RBAC integration is explicit:
download.readallows opening the downloads view and receiving transfer snapshots.download.writeallows queueing, pausing, cancelling, retrying, and removing transfers.
- Persisted queue recovery is tolerant of corruption. If
downloads_state.jsoncannot be deserialized,
Tuliprox renames it to a timestamped*_corrupt.*.jsonbackup and starts with an empty transfer queue instead of aborting server boot.
Note: The named capture group
(?P<episode>...)is mandatory for this to function correctly.Example:
.*(?P<episode>[Ss]\d{1,2}(.*?)[Ee]\d{1,2}).*
If Tuliprox itself must operate behind a corporate proxy or VPN (e.g., a Gluetun WireGuard container):
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.
To verify in the Web UI which public IP Tuliprox is currently using (crucial when verifying VPN routing or diagnosing geo-blocks), Tuliprox queries external detection APIs. This ensures that your traffic is actually routed through the intended gateway or VPN tunnel.
ipcheck:
url: "[https://api64.ipify.org?format=json](https://api64.ipify.org?format=json)" # Generic URL for both IP versions
url_ipv4: "[https://ipinfo.io/ip](https://ipinfo.io/ip)" # Dedicated IPv4 fetch
url_ipv6: "[https://v6.ident.me](https://v6.ident.me)" # Dedicated IPv6 fetch
pattern_ipv4: '(?:\d{1,3}\.){3}\d{1,3}' # Optional Regex for extraction
pattern_ipv6: '([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}'| Parameter | Type | Technical Impact |
|---|---|---|
url |
String | A generic endpoint that may return both IPv4 and IPv6 in a single response (often JSON). |
url_ipv4 |
String | A dedicated URL to fetch only the public IPv4 address. |
url_ipv6 |
String | A dedicated URL to fetch only the public IPv6 address. |
pattern_ipv4 |
Regex | Optional regex pattern to extract the IPv4 string from complex API responses (e.g., if the API returns a full JSON object). |
pattern_ipv6 |
Regex | Optional regex pattern to extract the IPv6 string from complex API responses. |
- VPN Validation: This feature is primarily used to confirm that Tuliprox is successfully using a VPN or Proxy. If the displayed IP in the Web UI matches your home ISP instead of your VPN provider, your routing is likely misconfigured.
- Regex Extraction: If your preferred IP-API returns data in a format like
{"ip": "1.2.3.4", "city": "Berlin"}, you can use thepattern_ipv4to isolate just the IP address for the Tuliprox UI. - Execution: The check is performed periodically or on-demand when accessing the Dashboard to provide real-time connectivity status.
Deep-Dive Feature: Tuliprox can masquerade on the local network as a physical SiliconDust HDHomeRun DVB-C/S/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) for broad compatibility and the proprietary SiliconDust protocol (UDP Port 65001) for compatibility with official HDHomeRun tools.
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 source.yml hdhomerun output!
tuner_count: 4 # Number of concurrent streams the client thinks are available
port: 5004 # Unique TCP Port for this specific virtual tuner 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"| Parameter | Type | Default | Description |
|---|---|---|---|
enabled |
Bool | false |
Master switch for the entire emulation engine. |
auth |
Bool | false |
Requires HTTP Basic Auth for the /lineup.json endpoint using the assigned user's credentials. |
devices |
List | [] |
A list of virtual HDHomeRun devices to emulate. |
| Parameter | Type | Default | Technical Impact & Background |
|---|---|---|---|
name |
String | Required | Unique internal identifier. Must match the device field in your source.yml target mapping. |
tuner_count |
Int | 1 |
Number of virtual tuners reported to the client. Defines how many concurrent streams Plex/Emby thinks the "hardware" can handle. |
port |
Int | API+1 |
TCP port for the HTTP API (/device.xml, /lineup.json). Each virtual device must have a unique port. |
friendly_name |
String | (Auto) |
The display name in client applications (e.g., "Tuliprox Living Room"). |
device_id |
Hex | (Auto) |
8-char hex ID for SiliconDust protocol (Port 65001). Tuliprox automatically corrects invalid IDs by calculating the required checksum. |
device_udn |
UUID | (Auto) |
Unique Device Name for UPnP/SSDP (Port 1900). Recommended to leave blank for auto-generation. |
manufacturer |
String | SiliconDust |
Customizes the manufacturer string reported to clients. |
model_name |
String | HDTC-2US |
Mimics a specific hardware model for maximum compatibility with official apps. |
firmware_name |
String | hdhomerun3_atsc |
The firmware type reported during the discovery handshake. |
Note: Advanced metadata fields like
model_numberandfirmware_versioncan also be overridden but are safe to leave at their defaults to ensure the best "plug-and-play" experience with media servers.
The name of each device in config.yml must correspond to a device reference in a hdhomerun output target within
your source.yml.
Example source.yml snippet:
targets:
- name: my-tv-lineup
output:
- type: hdhomerun
device: hdhr1 # Link to the device defined above
username: local # The user whose credentials/playlist will be servedTo satisfy both official SiliconDust hardware scanners and generic third-party UPnP discovery, Tuliprox handles two distinct layers:
device_id(Port 65001): Used by proprietary SiliconDust tools. It requires a specific hexadecimal format and checksum.device_udn(Port 1900): Used by the standard SSDP/UPnP protocol (e.g., by Plex or VLC). This identifies the device as a unique UUID on the network.
Pro-Tip: If Plex fails to find your device, ensure that the UDP ports 1900 and 65001 are not blocked by your firewall and that Tuliprox is running on the same network subnet as your media server.
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.
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 theirmax_connectionslimit)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'sexp_datereached)panel_api_provisioning.ts(Loops while a new Provider Account is generated via Panel API)hls_session_or_lease_expired.ts(Shared-HLS session or access lease expired; restart stream)
Note: These Video files are all available in the docker image.
How to create your own fallback video stream:
You can simply convert an image with ffmpeg.
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.tsThe filename identifies the file inside the path custom_stream_response_path.
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.