Files
tuliprox/docs/src/configuration/secrets.md
T
euzuandGitHub fa5d3237f1 added docker ulimits, .env support, ptt fix (#867)
- **New Features**
  - Added automatic `.env` loading for secrets and environment variables, with custom file paths and existing-environment precedence.
  - Added a `--env-file` option and a sample environment configuration template.
  - Increased Docker file descriptor limits for improved reliability under high connection counts.

- **Bug Fixes**
  - Fixed title parsing for multi-byte UTF-8 characters, preventing crashes and incorrect volume extraction.

- **Documentation**
  - Added guidance for `.env` configuration, Docker usage, restart requirements, and resolving file descriptor exhaustion.
2026-09-15 12:51:09 +02:00

215 lines
12 KiB
Markdown

# 🔐 Secrets & Environment Variables
Every deployment needs credentials: provider logins, output-user passwords, webhook tokens, web server secrets.
Tuliprox keeps the repository free of real credentials while making it easy to inject them at runtime.
> **Host-agnostic.** These instructions deliberately do not assume any particular host, container platform, or
> cloud provider. Secrets are read from the process environment and from files we generate on the machine itself,
> so the same workflow works whether you run a container, a service manager, a process supervisor, a small server,
> or a hosted secret store of your choice. The mechanism to supply an environment variable is always the same:
> it must be present in the environment of the `tuliprox` process at startup.
## What counts as a secret
| File | Field | Example env var |
|:------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------|
| `source.yml` | input `url`, `username`, `password` | `CLOUDTV_1_URL`, `CLOUDTV_1_USER`, `CLOUDTV_1_PASS` |
| `source.yml` | input `epg.sources[].url` | `CLOUDTV_1_EPG_URL` |
| `source.yml` | input `panel_api.api_key` (account management) | `PROVIDER_PANEL_API_KEY` |
| `api-proxy.yml` | output user `password` / `token` for every published user | `XTR_USER_LOCAL_PASS`, `XTR_USER_LOCAL_TOKEN` |
| `config.yml` | `web_ui.auth.secret` (JWT signing, 64-hex) | `TULIPROX_WEB_SECRET` |
| `config.yml` | messaging webhooks/tokens: Telegram bot token, Discord URL, Pushover token/key, ntfy/Gotify tokens, Slack URL, generic REST URL + `signing_secret` + `Authorization` headers | `TULIPROX_DISCORD_WEBHOOK`, `TULIPROX_TELEGRAM_TOKEN`, `TULIPROX_SIGNING_SECRET` |
| `config.yml` | `metadata_update.tmdb.api_key` | `TULIPROX_TMDB_API_KEY` |
| `config.yml` | `reverse_proxy.rewrite_secret` | `TULIPROX_PROXY_REWRITE_SECRET` |
| `config/user.txt` | Web UI Argon2 password hashes (see below — not env-injectable) | *file only* |
Anything a provider, a player, a notification bot, or a browser authenticates with is a secret and must not be in git.
## `${env:VAR}` interpolation
Every config file that Tuliprox reads (`config.yml`, `source.yml`, `api-proxy.yml`, `mapping.yml`, `template.yml`)
supports environment-variable interpolation with the syntax:
```text
${env:VAR_NAME}
```
Variable names match `[a-zA-Z_][a-zA-Z0-9_]*`.
- **How it works:** the placeholder is resolved *before* the file is parsed as YAML, from the environment of the
running process. If the variable is missing, Tuliprox logs
`Could not resolve env var 'VAR_NAME'` and leaves the literal `${env:VAR_NAME}` in place so the problem is visible.
- **Quoting:** keep string values quoted, exactly like the value the variable will expand to:
```yaml
inputs:
- name: provider
type: xtream
url: "${env:CLOUDTV_URL}"
username: "${env:CLOUDTV_USER}"
password: "${env:CLOUDTV_PASS}"
```
This stays valid YAML before and after substitution.
- **Use only for string fields.** Numbers (`exp_date`, ports, `token_ttl_mins`) and booleans (`enabled`) still belong
directly in the file; injecting them through env vars is fragile.
- **Not supported in `user.txt`.** Web UI credential files are read directly and must be written on the machine
(see below).
- **Also works in CLI paths:** any `--home` / `-c` / `-i` / `-a` argument can be `${env:...}` too.
### `.env` file support
Tuliprox automatically loads environment variables from a `.env` file at startup. This makes it easy to keep protected
credentials in a local `.env` file without committing them to source control (the repository's `.gitignore` already
ignores `.env` and `.env.*`). A sample template is provided at `config/.env.example`.
#### Discovery and precedence
Tuliprox searches for a `.env` file in the following order:
1. **Explicit CLI argument or environment variable:** `-e, --env-file <PATH>` or `TULIPROX_ENV_FILE=<PATH>`.
If explicitly specified, the file must exist and have valid syntax; otherwise Tuliprox exits with an error.
2. **Config file directory:** if `-c <PATH>` was provided, the parent directory of that file (e.g. `<config_file_dir>/.env`).
3. **Config directory:** `<config_path>/.env` (default: `<home_path>/config/.env`).
4. **Home directory:** `<home_path>/.env`.
5. **Current working directory:** `./.env`.
The first matching file found is loaded.
#### Docker usage
There are three common ways to use `.env` files with Docker:
- **Automatic via mounted `config` directory (Recommended):**
If your `docker-compose.yml` mounts `./config:/app/config`, simply place your `.env` file at `./config/.env` on the host.
Because Tuliprox starts with `-p /app/config`, it automatically discovers and loads `/app/config/.env` at startup.
- **Custom location via `TULIPROX_ENV_FILE`:**
If your secrets file is located elsewhere on the host, mount it into the container and point the `TULIPROX_ENV_FILE`
environment variable to it:
```yaml
services:
tuliprox:
image: ghcr.io/euzu/tuliprox:latest
environment:
- TULIPROX_ENV_FILE=/secrets/tuliprox.env
volumes:
- /etc/secrets/tuliprox.env:/secrets/tuliprox.env:ro
- ./config:/app/config
```
- **Native Docker Compose `env_file:` directive:**
Docker Compose can directly populate container environment variables from a `.env` file on the host without mounting:
```yaml
services:
tuliprox:
image: ghcr.io/euzu/tuliprox:latest
env_file:
- .env
```
#### 12-factor rules: Existing environment variables take precedence
Variables already defined in the system or container environment (e.g. via `export VAR=value`, Docker `environment:`,
or Kubernetes secrets) **are never overwritten** by a `.env` file. The `.env` file serves as a default/fallback
configuration.
#### ⚠️ Restart required on change (no dynamic hot-reload)
> **Important:** Changes to `.env` files do **not** take effect dynamically via configuration reload.
> For mounted `.env` files or bare-metal installations, restart the `tuliprox` service or container
> (`docker compose restart`). When using Docker Compose `env_file:`, recreate the container
> (`docker compose up -d --force-recreate`) to apply changed values.
>
> *Why:* In Rust, modifying environment variables in a multi-threaded runtime (`setenv`) is inherently
> not thread-safe and can cause undefined behavior or data races with concurrent readers. `.env` values are loaded
> strictly once at application startup and require a restart to change.
### Other ways of supplying variables
Because Tuliprox resolves from the process environment, any standard mechanism works alongside or instead of `.env`:
- `export TULIPROX_WEB_SECRET=...` in the shell / init script that starts the process,
- `environment:` entries in a container or compose file,
- `Environment=` lines of a service unit,
- the secret store / environment settings of whatever runtime you picked.
## Web UI credentials (`user.txt`)
Tuliprox stores password *hashes*, never plain text. Each line is `username:argon2_hash[:group1,group2]`; without
groups the user defaults to `admin`.
1. Generate a hash with the interactive CLI prompt (it needs a real terminal and cannot read from stdin):
```bash
tuliprox --genpwd
```
2. Write the line into `config/user.txt` (or the user file configured in `config.yml`):
```text
myuser:$argon2id$v=19$m=19456,t=2,p=1$...
```
3. Restart the server (or reload) and verify the login.
`user.txt` is environment-injectable only in the sense that the *path* may come from a CLI/`${env:...}` path; the
hashes themselves are generated on the machine and are never committed. The `config/user.txt` shipped in this
repository contains sample hashes for the demo accounts `test` / `nobody` documented in `config/README.md` — replace
them before going live.
## JWT secret (`web_ui.auth.secret`)
If `web_ui.auth.secret` is omitted, Tuliprox generates one in-memory and **all active logins are invalidated on every
restart**. For production, pin a static 64-character hexadecimal string and keep it stable:
```bash
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
Put the result in an environment variable and reference it from `config.yml`:
```yaml
web_ui:
auth:
secret: "${env:TULIPROX_WEB_SECRET}"
```
Rotating it invalidates all sessions — do it deliberately, not on a whim.
## Pre-publish checklist
Run these before pushing anything to a public repository:
```bash
# Anything obviously credential-like in tracked configs
git grep -n -i -E "(password|secret|token|api_key)[[:space:]]*:" -- config/
# Explicit values that should have been placeholders or generated on-site
git grep -n -i -E "your_|TODO|changeme|example|\.secret|localsecret" -- config/
# Staged files (never a secret should be here)
git diff --cached --stat
git status --porcelain
```
Other good habits:
- Keep `logging.sanitize_sensitive_info: true` (default). It masks passwords, provider URLs and client IPs in logs
so shared logs can't leak credentials.
- Keep `runtime_config_report_enabled: false` unless you need a startup dump; when enabled it masks sensitive values
as `***` anyway.
- Never commit `data/`, `target/`, `downloads/`, `cache/`, `backup/`, `.env`, or any runtime directory — the
repository `.gitignore` already covers the common ones.
- If a secret ever lands in history, rotate it (it is compromised history, not just a file) and rewrite history
with `git filter-repo` rather than committing new secrets on top.
## Related
- [`config.yml` core configuration](config.md) — `web_auth`, messaging, `metadata_update.tmdb`
- [`source.yml` inputs & providers](source.md) — provider credentials, EPG, backup URLs
- [`api-proxy.yml` published users](api-proxy.md) — output user credentials and tokens
- [Getting Started](../getting-started.md) — where config files live and how the home directory is resolved