mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-28 17:52:50 +01:00
- **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.
215 lines
12 KiB
Markdown
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
|