CLI & Environment¶
Everything tunlx can be told at startup, including flags, environment variables, and the file paths it cares about.
Command-line flags¶
| Flag | Default | Purpose |
|---|---|---|
-configFile |
./config.json |
Path to the JSON configuration. Auto-created on first run if missing, together with its directory. |
-dashboardPort |
6060 |
Override the dashboard port without editing config. |
-thisServerHost |
— | Override proxies[].thisServerHost for the legacy single-proxy mode. |
-thisServerPort |
— | Same, but for the listen port. |
-targetServer |
— | Same, but for the upstream URL. |
-debug / -v |
false |
Verbose logging at startup. Equivalent to TUNLX_DEBUG=true. |
--xtremeWizard |
— | Launch the interactive Xtream CLI wizard and exit. |
-resetAdminPassword |
— | Set a new admin password in the config file, print it and exit. Stop tunlx first. See Troubleshooting. |
-version |
— | Print the version and exit. The dashboard's About dialog and footer show the same value; quote it when you report a problem. |
-help |
— | Print the usage text and exit. |
Environment variables¶
Environment overrides are useful for container deployments and CI.
Core¶
| Variable | Equivalent in config | Purpose |
|---|---|---|
TUNLX_DEBUG |
— | true enables verbose startup logs. |
TUNLX_DASHBOARD_PORT |
dashboardPort |
Dashboard port. |
TUNLX_SESSION_SECRET |
sessionSecret |
32-byte key (base64 / hex / 32-char ASCII) for session cookies. |
TUNLX_ASSETS_DIR |
— | Read templates/ and static/ from this directory instead of the copies built into the binary. For working on the web UI; leave it unset otherwise. |
TUNLX_DISABLE_EXTERNAL_IP_LOOKUP |
— | true stops tunlx asking outside services (api.ipify.org and its fallbacks) for its public IP. The navbar then shows no address. See Networking. |
TUNLX_ADMIN_PASSWORD |
webCredentials.password |
Admin password (8–72 bytes) for a first start with no config, or for -resetAdminPassword. Without it a random one is generated, printed and saved as initial-admin-password.txt next to the config, and nothing but choosing a new one opens until it is changed. A password set here is yours: nothing asks you to change it. |
WireGuard¶
| Variable | Equivalent in config | Purpose |
|---|---|---|
TUNLX_WG_IFACE |
— | Interface name when route: vpn (default wg0). |
TUNLX_WG_CONF |
vpn.manual.configPath |
Path to the WireGuard config (default /etc/wireguard/<iface>.conf). |
TUNLX_WG_AUTOSTART |
vpn.autoStartOnBoot |
Enable the systemd unit for the interface (Linux). |
TUNLX_WG_MARK |
— | Firewall mark (SO_MARK) on WireGuard sockets. |
TUNLX_VPN_DEBUG |
— | 1, true or yes logs VPN provider diagnostics (e.g. Mullvad CLI calls and relay-list retries) at info level. Without it they log at debug level, so they only appear when debug logging is on. |
MediaFlow¶
| Variable | Equivalent in config | Purpose |
|---|---|---|
TUNLX_MEDIAFLOW_BASE_URL |
proxies[].mediaFlowURL |
Fallback when a proxy doesn't override the URL. |
API_PASSWORD |
— | Default API password for MediaFlow requests. |
MEDIAFLOW_API_PASSWORD |
— | Fallback if API_PASSWORD is unset. |
Tailscale (embedded tsnet)¶
These take effect at startup. Precedence is config.json → these env
vars → defaults (see tailnet_config.go).
| Variable | Equivalent in config | Purpose |
|---|---|---|
TUNLX_TS_ENABLED |
tailscale.enabled |
Turn the embedded tsnet node on. |
TUNLX_TS_HOSTNAME |
tailscale.hostname |
Hostname advertised to the Tailnet (default tunlx). |
TUNLX_TS_STATE_DIR |
tailscale.stateDir |
Where the node persists state (default: tailscale-state next to the config file). |
TUNLX_TS_CONTROL_URL |
— | Custom Headscale / control plane URL. |
TUNLX_TS_EPHEMERAL |
tailscale.ephemeral |
Register as an ephemeral node. |
TS_AUTHKEY |
tailscale.tsAuthKey |
Standard Tailscale auth key for first-time login. Takes precedence over TUNLX_TS_AUTHKEY if both are set. |
TUNLX_TS_AUTHKEY |
tailscale.authKey |
Legacy tunlx-specific auth key, kept for older deployments. Prefer TS_AUTHKEY for new ones. |
Install-time (for install.sh --silent)¶
| Variable | Purpose |
|---|---|
TUNLX_ADMIN_PASSWORD |
Set the initial admin password non-interactively. |
TUNLX_TAILSCALE_ONLY |
Set to 1 to bind only on the Tailnet. |
TUNLX_TAILSCALE_AUTHKEY |
Auth key the installer passes to the Tailscale node. |
TUNLX_TAILSCALE_HOSTNAME |
Hostname for the Tailscale sidecar. |
File locations¶
| Path | Purpose |
|---|---|
config.json |
Everything you configure. It is wherever -configFile points: ./config.json when you run the binary without the flag, /app/data/config.json in the container images (the installer and the baseline compose file mount ./data there). |
config.json.bak |
Next to the config file. The configuration as it was before the last bulk change that removes or replaces entries: Delete all categories, an M3U or iptv-org import that replaces a category, or an Import Proxies that overwrites. It is the file as stored, so as sensitive as config.json (stream addresses in it can carry a provider login) and kept private to its owner the same way; do not commit or share it. Copy it over config.json and restart to take that change back. Only the last copy is kept, and the change is refused if the copy cannot be written. |
config.key |
Next to the config file. The key that encrypts the secrets in it; keep the two together and never commit either. |
initial-admin-password.txt |
Next to the config file. The first-run admin password, until you change it. |
tailscale-state/ |
Next to the config file (/app/data/tailscale-state in the container images). The embedded Tailscale node's identity; TUNLX_TS_STATE_DIR or tailscale.stateDir moves it. |
/etc/wireguard/wg0.conf |
The WireGuard config (TUNLX_WG_CONF changes the path). wg0.conf.bak beside it is the config the last save replaced. |
examples/config.example.json |
Canonical sample with every field documented. |
Admin HTTP endpoints¶
A short list of endpoints the dashboard uses — handy for scripting:
| Method | Path | Purpose |
|---|---|---|
GET |
/metrics |
Host CPU / memory / network-IO JSON (container health) |
GET |
/config |
The running configuration (secrets as codes, see below) |
POST |
/config |
Save a configuration; codes are put back before it is read |
POST |
/config/reveal |
Exchange codes for the values they stand for |
GET |
/outbound-ip |
Detected egress IP (VPN-aware) |
GET |
/outbound-ip/non-vpn |
Egress IP via system route |
GET |
/tailnet/status |
Embedded Tailscale node state |
GET |
/tailnet/peers |
Tailnet exit-node candidates |
POST |
/tailnet/action |
Apply exit-node selection |
GET |
/tailnet/config |
Tailscale config snapshot |
POST |
/networking/config |
Set inbound + outbound modes |
POST |
/process/restart |
In-place re-exec (used after listener changes) |
GET |
/xtreme-service/tests |
Stream health test status |
GET |
/xtreme-service/tests/history |
Past stream-test results |
GET |
/integrations/iptv-org/metadata |
Available countries / categories from iptv-org |
POST |
/integrations/iptv-org/refresh |
Force-refresh the iptv-org cache |
POST |
/setup/state |
Mark dashboard onboarding complete |
All endpoints except /metrics require an authenticated session (admin
cookie) and sit behind the same rate limiters that protect the login and EPG
endpoints. /metrics is deliberately unauthenticated so the container health
check can reach it — treat the dashboard port as private and keep it
firewalled from the public internet.
Secrets in the answers¶
GET /config, GET /proxies, GET /vpn/wireguard-config?metadata=true and
the Xtream service's groups, channels and stream tests never carry a provider
password, a viewer's password hash, a token, a WireGuard key or the login inside
a stream address. (GET /vpn/wireguard-config without metadata=true still
answers with the file as plain text, keys included, for scripts that download
it.) Each secret comes as a code: tunlx-secret.v1. and 22 characters for a
value, tunlx-secret.v1u. and 22 for one part of an address
(/live/alice/tunlx-secret.v1u.…/1001.ts). The same value always gets the same
code, also after a restart, and a code is no use without the admin session.
Send a code back where it came from and the server puts the value back before
anything reads it, so a script that fetches /config, changes one field and
posts it back keeps every secret. To read a value, post the codes to
/config/reveal:
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-d '{"handles":["tunlx-secret.v1.AbCdEfGhIjKlMnOpQrStUv"]}' \
http://localhost:6060/config/reveal
# {"missing":[],"values":{"tunlx-secret.v1.AbCdEfGhIjKlMnOpQrStUv":"the password"}}
A code the server does not know (the value was changed since it was fetched, or
it came from another instance) is refused with 409 and a sentence that says to
reload; /config/reveal lists it under missing. Every call to
/config/reveal is logged as secrets shown on request, with a count and no
values.
Quick "what's running" checklist¶
# Process is up?
curl -s http://localhost:6060/metrics | head
# What does egress look like right now?
curl -s http://localhost:6060/outbound-ip
curl -s http://localhost:6060/outbound-ip/non-vpn
# Tailscale up?
curl -s http://localhost:6060/tailnet/status
# Container logs (Docker / Compose)
docker logs --tail=200 tunlx