iArt.ai
AI 모션 그래픽 소프트웨어, 누구나 사용할 수 있는 After Effects 대안, 텍스트 프롬프트로 몇 분 안에 애니메이션·설명 영상 제작
Optional agent for unarr.app — connect your sources, organize your library and stream it to your devices. Windows, macOS, Linux, Docker.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
⚠️ Active development — unarr is under active development. Features may change, and bugs are expected. Report issues here. The single-binary terminal client for torrent, debrid, and usenet downloads. Free and open source. Built-in torrent engine, debrid (Real-Debrid / AllDebrid), and NZB support. Stream to mpv/vlc, transcode on the fly with hardware acceleration, and manage your library — one binary or a headless daemon with WireGuard split-tunnel and Cloudflare Funnel remote access.README 전문 보기 ↗
⚠️ Active development — unarr is under active development. Features may change, and bugs are expected. Report issues here.
The single-binary terminal client for torrent, debrid, and usenet downloads. Free and open source.
Built-in torrent engine, debrid (Real-Debrid / AllDebrid), and NZB support. Stream to mpv/vlc, transcode on the fly with hardware acceleration, and manage your library — one binary or a headless daemon with WireGuard split-tunnel and Cloudflare Funnel remote access.
curl -fsSL https://unarr.app/install.sh | sh
irm https://unarr.app/install.ps1 | iex
brew install unarr-app/tap/unarr
Published on every release for amd64 and arm64, and covered by the same
signed checksums.txt as the tarballs:
# Debian/Ubuntu — replace <version> and <arch> (amd64 | arm64)
curl -fsSLO https://github.com/Unarr-app/unarr-cli/releases/latest/download/unarr_<version>_linux_<arch>.deb
sudo apt install ./unarr_<version>_linux_<arch>.deb
# Fedora/RHEL
sudo dnf install https://github.com/Unarr-app/unarr-cli/releases/latest/download/unarr_<version>_linux_<arch>.rpm
The package installs the unarr binary only. Run unarr init afterwards to
link your account, pick a download directory and install the background
service — the same step the other install methods end with. ffmpeg is a
recommended dependency; without it the agent downloads its own static copy.
docker run -d --name unarr \
--restart unless-stopped \
--network host \
--read-only --memory 512m \
-v ~/.config/unarr:/config \
-v ~/Media:/downloads \
unarr/cli
Run setup first to configure your API key:
docker run -it --rm \
-v ~/.config/unarr:/config \
unarr/cli setup
mkdir -p unarr && cd unarr
curl -fsSL https://raw.githubusercontent.com/Unarr-app/unarr-cli/main/docker-compose.yml -o docker-compose.yml
docker compose up -d
services:
unarr:
image: unarr/cli:latest
container_name: unarr
restart: unless-stopped
user: "1000:1000"
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
volumes:
- ./config:/config
- ~/Media:/downloads
- unarr-data:/data
environment:
- TZ=${TZ:-UTC}
# - UNARR_API_KEY=tc_your_key_here
deploy:
resources:
limits:
memory: 512M
cpus: "2.0"
# Host network for full P2P performance
network_mode: host
# Or use bridge with ports:
# ports:
# - "6881-6889:6881-6889/tcp"
# - "6881-6889:6881-6889/udp"
volumes:
unarr-data:
go install github.com/Unarr-app/unarr-cli/cmd/unarr@latest
Download prebuilt binaries for Linux, macOS, and Windows from GitHub Releases.
git clone https://github.com/Unarr-app/unarr-cli.git
cd unarr-cli
make build
# 1. Run the init wizard (opens browser for API key)
unarr init
# 2. Search for content
unarr search "breaking bad" --type show --quality 1080p
# 3. Start the download daemon
unarr start
| Command | Description |
|---|---|
unarr init | First-time configuration wizard (API key, download dir, daemon) |
unarr login | Authenticate with your account (opens browser) |
unarr config | Edit all settings interactively (speed, organization, etc.) |
unarr config check | Validate config.toml — unknown keys (with suggestions) and out-of-range values; exits non-zero when anything is reported, or with the daemon's own error when the file does not load at all (invalid TOML, a value of the wrong type) |
unarr config mount | Configure the optional local mount; provider accounts and credentials are managed on the web |
unarr migrate | Import settings and wanted list from Sonarr/Radarr/Prowlarr [pre-beta] |
| Command | Description |
|---|---|
unarr search <query> | Search for movies and TV shows with advanced filters |
unarr inspect <magnet|hash|name> | TrueSpec analysis — quality, codec, seed health |
unarr popular | Show popular movies and TV shows |
unarr recent | Show recently added content |
unarr watch <query> | Find where to watch — streaming + torrents |
| Command | Description |
|---|---|
unarr download <hash|magnet> | One-shot download (no daemon needed) |
unarr downloads | List the download queue (alias: unarr dl) |
unarr downloads pause|resume <id> | Pause a download / continue it |
unarr downloads stop <id> | Cancel it and stop it coming back after a restart |
unarr downloads retry <id> | Start it over |
unarr downloads purge | Forget queued downloads that are not running |
unarr stream <hash|magnet> | Stream a torrent directly to mpv/vlc/browser |
unarr mount <directory> | Configure and activate the persistent read-only remote folder (directory is optional) |
unarr umount | Disable and unmount the remote folder (unarr unmount is an alias) |
unarr mount serve | Serve that remote library over loopback WebDAV without mounting it |
See Controlling downloads for the flags, the offline recovery path, and how these interact with the website.
| Command | Description |
|---|---|
unarr scan <path> | Scan a folder, analyze video files with ffprobe, sync quality data |
unarr library clean | Sweep download & library dirs for orphaned files (dry-run by default) |
unarr library stats | Report library health, composition & quality — read-only (--json for scripts) |
unarr organize preview | Show the folders/file names new downloads get with your naming layout (or try --naming plex, --series-format …) — touches nothing |
| Command | Description |
|---|---|
unarr start | Start the download daemon (foreground) |
unarr stop | How to stop the running daemon |
unarr status | Show daemon status and active downloads (--sessions for live sessions, --json for raw) |
unarr daemon install | Install as system service (systemd/launchd) |
unarr daemon uninstall | Remove the system service |
unarr vpn status | Show managed-VPN config and live tunnel state |
unarr vpn enable | Turn the managed VPN on |
unarr vpn disable | Turn the managed VPN off |
| Command | Description |
|---|---|
unarr stats | Show catalog statistics |
unarr doctor | Diagnose configuration and connectivity |
unarr report | Prepare and review private technical events; save locally or separately consent to upload (alias: unarr reports). See privacy and delivery availability. |
unarr mirrors | Manage mirror failover list (list / update / test) |
unarr logs | Read the daemon log — -f, --since, --level, --grep, -n (alias: unarr daemon logs) |
unarr clean | Remove temporary files, logs, and cached data |
unarr backup | Back up / restore config and the download queue (create / list / restore / prune). See Backup |
unarr upgrade | Update unarr to the latest version (alias: unarr self-update) |
unarr version | Show version info |
unarr completion <shell> | Generate shell completion scripts |
Search the catalog with advanced filters. Results include quality scores, seed health, and metadata from 30+ sources.
unarr search "inception" --sort seeders --min-rating 7 --lang es
unarr search "breaking bad" --type show --quality 1080p
unarr search "matrix" --json | jq '.results[].title'
Filters:
| Flag | Description | Values |
|---|---|---|
--type | Content type | movie, show |
--quality | Video quality | 480p, 720p, 1080p, 2160p |
--lang | Audio language (ISO 639) | es, en, fr, de, ... |
--genre | Genre | Action, Comedy, Drama, Horror, ... |
--year-min | Minimum release year | 2020 |
--year-max | Maximum release year | 2026 |
--min-rating | Minimum IMDb/TMDb rating | 0-10 |
--sort | Sort order | relevance, seeders, year, rating, added |
--limit | Results per page | 1-50 |
--page | Page number | 1, 2, ... |
--country | Country for streaming info | US, ES, GB, ... |
TrueSpec analysis — parse a torrent and show detailed quality specs.
unarr inspect "Oppenheimer.2023.1080p.BluRay.x265"
unarr inspect abc123def456abc123def456abc123def456abc1
unarr inspect "magnet:?xt=urn:btih:ABC123&dn=Movie.2023.1080p"
Accepts magnet URIs, 40-character info hashes, or torrent file names. Shows quality, codec, size, seeds, languages, source, quality score, health, and alternatives.
Find where to watch — streaming services alongside torrent options.
unarr watch "oppenheimer" --country ES
unarr watch "breaking bad" --json
Shows legal streaming options first (subscription, free, rent, buy), then torrent alternatives.
Stream a torrent directly to a media player without waiting for the full download.
unarr stream abc123def456abc123def456abc123def456abc1
unarr stream "magnet:?xt=urn:btih:..." --port 8080
unarr stream <hash> --player mpv
unarr stream <hash> --no-open # just print the URL
Downloads pieces sequentially and serves the video over a local HTTP server. Auto-detects mpv, vlc, or your default browser.
Subtitles. When the source file contains embedded text subtitles (SRT, ASS, PGS in an MKV), the daemon extracts them as WebVTT sidecars during the same transcode pass and serves them alongside the HLS stream. The web player lists available subtitle tracks automatically — no separate subtitle download needed.
Seek-anywhere (copy-VOD). For sources with browser-compatible codecs (H.264 + AAC), the daemon uses a copy pass instead of re-encoding. This enables full random-seek across the entire duration from the first play, at near-zero CPU cost.
Audio tracks. Multi-audio MKVs expose all tracks (e.g. fr, en, es) in the player's audio menu. Switching tracks starts a new session from the current position.
One-shot download by info hash or magnet link (no daemon required).
unarr download abc123def456abc123def456abc123def456abc1
unarr download "magnet:?xt=urn:btih:..." --method torrent
unarr downloads lists and controls whatever the daemon on this machine is
working on. It talks to the daemon directly over loopback — no website, no
account lookup — so it keeps working when the server is unreachable or the
download has fallen out of the dashboard.
unarr downloads # what is running right now (alias: unarr dl)
unarr downloads pause # menu: tick what to pause
unarr downloads stop # menu: tick what to stop
unarr downloads pause --all # pause everything
unarr downloads stop --all # stop everything (asks to confirm)
unarr downloads pause 31ec4169 # or name it directly
unarr downloads resume 31ec4169 # continue where it left off
unarr downloads stop 31ec4169 --delete # cancel and delete the partial file
unarr downloads retry 31ec4169 # start it over
unarr downloads purge # forget queued leftovers
unarr downloads --json # machine-readable, for scripts
Run an action with no id and you get a menu of the downloads it can apply
to — space to tick, enter to confirm, esc to cancel. Only the relevant ones are
listed: pause shows what is running, resume shows what is not.
Ids may be abbreviated to any unique prefix — the 8-character ids printed here and in the logs are enough. An ambiguous prefix is refused rather than guessed.
Two actions ask before they run: --delete (files leave the disk) and
stop --all (nothing resumes by itself afterwards). --yes skips the question,
and in a script or a pipe — where there is no terminal to answer on — the
command refuses instead of hanging.
unarr download stop|pause|resume|retry|list|purge works too, as an alias.
Actions reach the website. Whatever you do here (or from the desktop tray) is reported to unarr.app, so the dashboard reflects it within a few seconds instead of showing a download that stopped minutes ago. It works the other way round as well: cancelling on the website stops the download on this machine.
If a download survives cancelling on the website, its task row is gone — you cancelled it and then removed it from the list — so the server has no way left to reach the agent, while the agent keeps restarting it from its resume queue on every launch.
The agent now detects this by itself: a task the server no longer knows about is dropped after a few status reports. To end it immediately:
unarr downloads stop <id> # daemon running
With the daemon stopped, --force edits the on-disk resume queue instead — the
file that would otherwise bring the download back:
unarr downloads # lists the queue even with no daemon
unarr downloads stop <id> --force
unarr downloads purge --force # clear the whole queue
The queue lives at active-tasks.json in the data dir
(~/.local/share/unarr/ on Linux, ~/Library/Application Support/unarr/ on
macOS, %LOCALAPPDATA%\unarr\ on Windows). Deleting it only forgets pending
downloads — media files and config are untouched.
unarr-desktop lists the active downloads in its menu with Pause / Resume /
Stop / Stop and delete files under each one. Same control plane, same
reporting back to the website. Beyond five downloads the menu points at
unarr downloads, which has no limit.
The daemon receives download tasks from the web dashboard and executes them automatically.
# Start in foreground (Ctrl+C to stop)
unarr start
# Or install as a system service (auto-starts on boot)
unarr daemon install
# Check status
unarr status
# Uninstall the service
unarr daemon uninstall
The daemon connects via WebSocket for instant task delivery, with automatic HTTP fallback. It supports torrent, debrid, and usenet downloads concurrently, reports progress to the web dashboard, and handles graceful shutdown.
Service locations:
~/.config/systemd/user/unarr.service (systemd)~/Library/LaunchAgents/com.torrentclaw.unarr.plist (launchd)The daemon keeps one view of everything that is moving right now — downloads,
in-browser HLS streams and the remote-folder mount — with each session's rate,
bytes moved, uptime, health (ok / degraded / stalled) and stall count.
unarr status --sessions # table
unarr status --sessions --json # the same snapshot the website reads
--json only changes --sessions: a plain unarr status --json still prints
the usual text status.
A session is degraded as soon as it is impaired (a download receiving no data, a transcode below realtime, a mount read that moves no bytes) and stalled once that lasts 30 s. Each stall is counted once, not once per check, and it only ends after 10 s of healthy flow, so a rate flapping around the threshold is one stall.
The daemon sends that same snapshot to your unarr account, so the website's sessions page can show it. It is on by default and does not depend on the Prometheus switch below.
[metrics]
publish = false # stop sending the session snapshot to the web
unarr status --sessions --json document (see
internal/metrics/SCHEMA.md) and the agent id:
session kind, state, health, rates, bytes, progress, stall counts, the VPN
state and the session titles — the release or file names of what the website
already asked this machine to download or play. No file paths, IPs, tokens or
URLs: a title that looks like an absolute path is cut to its last element, and
one that looks like a URL is sent empty.publish takes effect after unarr daemon restart.Off by default: nothing listens until you opt in.
[metrics]
enabled = true
# listen = "127.0.0.1:9877" # default: loopback only
# token = "change-me" # required if listen is not loopback
GET /metrics returns the Prometheus text format and always needs a token, sent
as Authorization: Bearer <token> (or the X-Unarr-Control-Token header). With
no token set, the control-plane token is used — it is in the daemon state file,
but it changes on every restart, so for a Prometheus server set your own token.
A non-loopback listen without a token is refused.
# prometheus.yml
scrape_configs:
- job_name: unarr
authorization: { credentials: change-me }
static_configs: [{ targets: ["127.0.0.1:9877"] }]
| Metric | Type | Labels |
|---|---|---|
unarr_info | gauge | version |
unarr_uptime_seconds | gauge | |
unarr_sessions_active | gauge | kind = download, hls, stream, mount |
unarr_sessions_stalled | gauge | |
unarr_session_bytes_total | counter | kind |
unarr_session_rate_bytes_per_second | gauge | kind |
unarr_session_stalls_total | counter | kind |
unarr_vpn_up, unarr_vpn_enabled, unarr_vpn_required | gauge | |
unarr_downloads | gauge | state (downloading, paused, …) |
There are no per-title or per-hash labels, so cardinality stays fixed. The format
is written by hand instead of pulling in client_golang, which would add several
MB and a chain of dependencies to the binary for ten fixed series.
GET /sessions (local control plane)The same snapshot is served to local tools (the website's unified view, the
desktop tray) at GET /sessions on the loopback control plane, with the
X-Unarr-Control-Token from the daemon state file. The JSON schema
(schemaVersion 1) is documented in internal/metrics/SCHEMA.md.
unarr can route your downloads through a managed WireGuard VPN, so peers and
trackers see the VPN server's IP instead of yours. It runs entirely in userspace
(wireguard-go + a gVisor netstack) — no root, no wg-quick, no changes to your
OS routing table.
# Turn it on (writes [downloads.vpn] enabled = true to your config)
unarr vpn enable
# Restart the daemon so it brings the tunnel up at startup
unarr daemon restart # or: unarr start (if not installed as a service)
# Check it's working — shows the exit server when the tunnel is up
unarr vpn status
# Verify your account is provisioned (queries the API)
unarr vpn status --check
# Turn it off again
unarr vpn disable
Split-tunnel — read this: only the torrent client's traffic goes through the
VPN. Your browser, curl, and every other app keep using your real IP — that
is by design. To check the VPN is working, look at unarr vpn status (or the
peer/announce IP), not your browser's "what's my IP". To protect your other
devices (phone, laptop), use the OpenVPN credentials from your profile — those
support ~10 concurrent devices and do not share the agent's WireGuard slot.
When does it fetch the config? Once, at daemon startup. There's no periodic
refresh — after changing your exit server in the web panel or re-provisioning,
restart the daemon to pick it up. If the fetch fails the daemon logs a [vpn]
line and downloads in the clear (never refuses to run).
Self-hosted / personal VPN: instead of the managed config, point unarr at a
local WireGuard .conf:
[downloads.vpn]
config_file = "/path/to/wg.conf" # takes precedence over `enabled`
# Run all diagnostic checks
unarr doctor
# Update to the latest version
unarr self-update
unarr self-update --force # reinstall even if up to date
unarr doctor checks: config file, API key, server connectivity (with latency), agent registration, download directory, disk space, and version.
unarr supports three update paths. Pick whichever fits your workflow.
1. Manual self-update (always available).
unarr self-update # interactive update to latest
unarr self-update --force # reinstall same version
unarr self-update --allow-unsigned # accept releases without checksum signature
The CLI downloads the new release archive over HTTPS (from
GitHub Releases at github.com/Unarr-app/unarr-cli/releases/download/v<ver>/,
falling back to the web origin if GitHub is unreachable), verifies SHA-256,
swaps the binary in place (.backup kept next to it), and restarts the systemd
user unit if the daemon is running.
2. Auto-apply on server signal (default, since 0.9.6).
When you press "Force update now" on the web (Settings → Agent → Force
update), the server sets a flag your daemon polls every sync (~3 s). On
the next sync the daemon downloads the new binary, replaces itself, and
exits — systemd Restart=always respawns on the new version. No SSH, no
terminal access required. Works headless on NAS / Docker.
The button shows an amber warning if your agent is below 0.9.6 (older daemons see the signal but only log "run unarr update" — the operator must run the command manually that one time).
Opt out of auto-apply. Some users prefer reviewing CHANGELOG before
applying. Disable in config.toml:
[daemon]
auto_upgrade = false
With auto_upgrade = false, pressing the web button still flags your
agent (so the daemon logs the new version on next sync), but the daemon
will not download / replace anything — you run unarr self-update when
you're ready.
3. Docker auto-restart with a new tag.
docker pull unarr/cli:latest
docker compose up -d
Tags published: latest, 1.2, 1.2.2, ... — pin to a minor (1.2)
for opt-in patch updates without surprises.
unarr logs # last 50 lines
unarr logs -f # follow
unarr logs -n 200 --level warn # only warnings and errors
unarr logs --since 2h --grep 'usenet|nzb' # last 2 hours, matching a regex
unarr logs --since "2025-01-20 09:00" --level error
unarr logs --boot # startup output — when it won't start
unarr logs rotate # trim now, if rotation is on and over budget
unarr daemon logs is the same command (kept as an alias, -f / -n
included). On Linux, where the systemd unit sends output to the journal, this
streams from journalctl --user -u unarr instead of a file; everywhere else it
reads unarr.log from the data directory and transparently continues into the
rotated copies when the live file holds fewer lines than you asked for.
Severity is inferred from the line itself (the daemon logs free-form text), so
--level is a reading aid, not a guarantee. --grep is a case-insensitive
regular expression. --since takes an age (30m, 2h, 7d, 1w) or a stamp
(2025-01-20, 2025-01-20 09:00, 09:00 for today).
Two files. A daemon started by a service launcher owns unarr.log and
writes everything it logs there. The launcher itself captures a second, much
smaller unarr.boot.log: the start banner, a fatal error from a start that
never got going, a crash dump — output that never reaches the daemon's own log.
unarr logs --boot reads it, and that is where to look when the daemon does not
come up at all. On a systemd install there is no such file: the startup output
is in the journal, so unarr logs already shows it and --boot says so.
On a systemd install, log_max_size_mb and log_max_files do nothing at all.
The unit sends the daemon's output to the journal, so there is no unarr.log to
rotate and retention is journald's business, not unarr's — set it in
journald.conf (SystemMaxUse, MaxRetentionSec); the defaults are 10% of the
filesystem, capped at 4 GB. Check what it currently costs with
journalctl --user -u unarr --disk-usage. Setting log_max_size_mb = 1 there and
expecting a bounded disk is the one misreading worth calling out: it bounds
nothing. (unarr logs rotate is correspondingly a no-op that exits 0 — it will
not conjure an empty unarr.log just to have something to rotate.)
Rotation is OPT-IN, and off by default (log_max_size_mb = 0). Out of the
box unarr never rotates anything: unarr.log and unarr.boot.log just grow.
Bound them with unarr clean, or with an external logrotate — and if you use
one, it must use copytruncate, since the daemon holds its descriptor for the
whole run and has no reopen signal.
Set log_max_size_mb to a number of MB to turn rotation on. unarr.log is then
rotated once it reaches that size, keeping log_max_files copies (default 3),
and unarr.boot.log gets its own fixed 2 MB cap with one rotated copy —
independent of the size you set, but switched on by the same key. A running
daemon rotates its own log by renaming it aside, so rotation does not wait for a
restart; unarr logs rotate is for the case where the daemon is stopped, and it
says so rather than doing nothing if a running daemon owns the file.
What you accept by turning it on. Rotation is not a solved problem here, and
this is why it does not ship on. Any second process holding the log can block
the rename or the truncate — an open unarr logs -f, an antivirus scanner,
OneDrive or Dropbox syncing the data directory, Windows Search. Windows is the
strict case: a holder there can deny write access outright, so a rotation that
cannot proceed is reported and the log keeps growing. Residual failure modes
that remain even when it does proceed (a reader holding a rotated slot, two
rotators racing over one staging file, a live-but-offline daemon losing its
ownership claim) are listed under "Deuda abierta" in
docs/plans/daemon-log-ownership.md —
read it before enabling this on an install you care about. On Windows only, the
boot log's trim is baked into the launcher script at install time, so re-run
unarr daemon install after enabling rotation for that one to take effect.
The global --log-level flag overrides [daemon] log_level for a single
command.
Remove temporary files, logs, resume data, and other artifacts generated by unarr. Shows what will be removed and asks for confirmation before deleting.
unarr clean # Show files and confirm before removing
unarr clean --dry-run # Show what would be removed (no prompt)
unarr clean --yes # Skip confirmation
unarr clean --all # Also remove the data directory
Cleans: log files (including the rotated unarr.log.1, .2, … copies), daemon state, stale usenet resume files (> 7 days), stale replaced-file backups (> 7 days, kept by library upgrades), stream temp data, upgrade temp files, and stale atomic-write temps. Recent resume files are kept to preserve download progress for paused or interrupted downloads. Never removes your config file, downloaded media, or partial torrent/debrid downloads.
Snapshot your local unarr state into one verified zip, and restore it later or on another machine.
unarr backup create # → <config dir>/backups/unarr-backup-<UTC stamp>.zip
unarr backup create --out ~/unarr.zip # a file, or a directory
unarr backup create --encrypt # passphrase-encrypted, secrets included
unarr backup list # tags [encrypted] [scheduled] [pre-restore]
unarr backup restore unarr.zip # dry-run: verifies and shows what would change
unarr backup restore unarr.zip --apply # writes (daemon must be stopped)
unarr backup prune --keep 3 # drop all but the newest 3 manual/scheduled backups
unarr backup prune --pre-restore # trim old pre-restore snapshots (newest 3 + last 30 days kept)
What is in it: config.toml and active-tasks.json (the download resume queue), plus a manifest.json (unarr version, schema, OS/arch, date, SHA-256 and size of every file). An archive holds up to 128 MiB of files (uncompressed, all together — some 150,000 queued tasks), of which config.toml may take at most 4 MiB and manifest.json 1 MiB (a real config is well under 100 KB). create checks those bounds, and that config.toml parses and the daemon can load the queue, before writing anything: it refuses an archive that restore would refuse, so every backup it writes can be restored. What is never in it: anything ephemeral or machine-bound — daemon.state.json (PID and the local control-plane token), unarr.lock, the agent TLS private key under certs/, logs, caches (including the library.json scan cache, rebuilt by a rescan), partial-download progress and your media.
Secrets — excluded by default. A plain archive is a file people copy to a NAS or a cloud folder, so it must be safe to lose. [auth] api_key, [agent] agent_hash and [downloads] webdav_username / webdav_password and [metrics] token are blanked in the copy of config.toml (the file stays valid TOML; your comments and layout are untouched) and resolved download URLs / NZB passwords are stripped from the queue. The result is checked by parsing it: if a credential is written in a form unarr cannot blank (a dotted key, an inline table, a multi-line string), the backup is refused rather than written with the secret inside — use --encrypt for such a config. Restoring a secret-free archive keeps the credentials already on the machine (the dry-run names any it cannot carry over; if the archive's auth.api_url or auth.mirrors would send them to a server this machine does not use, the dry-run and --apply warn — a warning, not a refusal, since pointing a machine at another server is legitimate; when this machine's own config.toml does not parse, so there is nothing to compare against, they say so and name the archive's auth.api_url and auth.mirrors the credentials will go to) and the [agent] id they belong to: when this machine already has an api_key or agent_hash, its own id stays instead of the archive's — even an empty one, in which case [agent] id is left empty rather than paired with credentials that are not its own — and both the dry-run and --apply say so (run unarr login if this is a new machine). On a machine with no credentials yet, the archive's id is restored; run unarr login afterwards. Debrid downloads in a restored queue have no link of their own and need a fresh one from the server. --encrypt is the opt-in for a full copy: the whole archive is sealed with AES-256-GCM using a key derived from your passphrase (PBKDF2-HMAC-SHA256, 600k rounds, random salt and nonce per archive — Go standard library only). The passphrase comes from a prompt, --passphrase-file, or UNARR_BACKUP_PASSPHRASE; an empty one is refused, and it cannot be recovered. VPN keys live in the .conf that [downloads.vpn] config_file points to; that file is not backed up.
Restore is safe by default. It refuses a bad manifest, a newer schema, any checksum mismatch, an archive or a file over its size bound, any archive member that is not one of the two known files (so a crafted ../ path cannot write elsewhere) or not a regular file (a symlink, a directory, a device), and content unarr could not load — a config.toml that does not parse the way unarr reads it (checked as it would be written, with this machine's credentials merged in) or a queue the daemon would drop on load (decoded with its own task type: one field of the wrong type, such as a queuedAt that is not a time, and it starts with an empty queue). Those checks run before anything is written or snapshotted. A restore is all or nothing: a queue an older unarr backed up that the daemon cannot load blocks the config.toml beside it too (there is no partial restore). A secret-free archive is also refused when this machine's own credentials are written in a form unarr cannot load, since they would be merged in: move config.toml aside, restore, then unarr login. The checksums prove integrity, not authorship: whoever holds an unencrypted archive can edit it and recompute them, so restore only archives you made — or encrypted ones, which the passphrase authenticates. A secret-free archive whose config.toml sets a credential anyway was edited after it was made, and is refused. An unencrypted archive with secrets included (what a pre-restore snapshot is, or an edited archive relabelled) is restored, but when its api_key / agent_hash are not this machine's, or it changes auth.api_url without being this machine's own identity, the dry-run and --apply warn. Without --apply nothing is written. With --apply it takes the daemon's instance lock for the whole restore — so it refuses to run while the daemon is up (unarr stop first) and a daemon cannot start halfway through — saves your current state as unarr-prerestore-<stamp>.zip in <config dir>/backups (a verbatim copy with your current secrets, 0600; it stays beside config.toml even when [backup] dir points elsewhere — --dir overrides; it is taken even when the current config.toml does not parse or is over its 4 MiB cap, so a good backup can replace a broken config, though restore will not bring that broken file back — unzip the snapshot for it; only the 128 MiB bound applies, and past it the error names the file to move aside before running the restore again), then replaces each file atomically. If a write fails, the files already replaced are put back. Because every snapshot holds secrets in the clear, each successful --apply then trims the older ones: the newest 3 and any younger than 30 days are kept, and never the one just taken. A failed restore removes nothing, and applies the same retention on demand (plain and the daemon never touch snapshots). A symlinked is updated through the link. An encrypted archive — or a secret-free one restored before — brings the archived agent id with it: restore it onto a replacement machine, not onto a second one while the original still runs, or both daemons share one identity.
Scheduled backups run inside the daemon (restart it after changing them). They are always secret-free, and a backup missed while the daemon was off is taken at its next start. An invalid table is logged at start-up and not scheduled; a failed backup is retried an hour later:
[backup]
enabled = true # default: false
frequency = "daily" # "daily" or "weekly"
time = "03:00" # local time, 24h
# weekday = "sunday" # weekly only
retention_count = 3 # scheduled backups to keep
# dir = "/srv/unarr-backups" # default: <config dir>/backups
retention_count only ever deletes scheduled backups (unarr-scheduled-<stamp>.zip): a manual archive — say the encrypted one holding your secrets — is never pruned behind your back. unarr config check validates this table. dir must be an absolute path: a relative one would depend on the directory unarr happens to run from, so the daemon does not schedule it and unarr backup create / list / prune refuse it before touching anything (their --dir and --out flags are ordinary command-line paths, relative to where you run them). The default folder is next to the config.toml in use (so --config gets its own) rather than in the data dir, because unarr clean --all wipes the data dir on Linux and Windows. On macOS the data dir is the config dir, so unarr clean --all removes the backups too — point dir elsewhere there.
unarr library clean reconciles your download directory and Movies/TV library dirs, reporting — and with --apply, removing — the deterministic junk that accretes there. It never removes a valid, playable video (>= the plausibility floor, default 1 MiB), and only removes duplicates after a full byte-for-byte compare confirms they are identical (a cheap fingerprint just finds the candidates). Everything acted upon is confined to the configured download/movies/tv directories.
unarr library clean # report only (DRY-RUN — the default)
unarr library clean --apply # actually remove the reported orphans
unarr library clean --dry-run # force report-only even with --apply (explicit preview)
unarr library clean --dedup-only # only collapse byte-identical duplicate videos
Reports / removes:
.part, .!qB, .aria2, .tmp, .partial with no active download..srt/.nfo/.jpg/.par2/… with no owning video. Per-track sidecars in a .unarr/ cache dir are checked against the parent release dir, so they are only removed when the release itself no longer holds the video.movie.mkv/ (a mis-created dir).remove_corrupt_videos) — a video of the right size (>= floor, so not a stub) whose first and/or last 1 MiB is all zero bytes: a corrupt/zero-filled download that is unplayable yet passes the floor and dodges the dedup (its fingerprint differs from the real copy). This is a strong-but-not-absolute heuristic, so it defaults off and is removed only by this manual command with the toggle on — never by the daemon's automatic sweep. unarr library stats always counts these (shown as Suspect (zero-content)) so you can see them even without enabling removal.The same sweep runs automatically after every library auto-scan in the daemon — it is the native, always-on replacement for an external cleanup script. It applies only the safe, deterministic categories, protects any partial modified in the last few minutes (a live download), logs every action, and reports the space freed. Configure it under [library.cleanup]:
[library.cleanup]
enabled = true # run automatically after each auto-scan (manual command always works)
min_video_bytes = "1MiB" # anti-stub floor; a video smaller than this is a stub
remove_stubs = true # delete videos below min_video_bytes
remove_orphan_partials = true # delete .part/.!qB/.aria2/.tmp/.partial with no active task
dedup_exact = true # collapse byte-identical duplicate videos (keeps one canonical copy)
remove_orphan_subtitles = true # delete sidecars with no owning video (.unarr → checks parent dir)
prune_empty_dirs = true # remove video-less dirs and media-named dirs
remove_corrupt_videos = false # OPT-IN: flag/remove right-sized videos whose first/last 1 MiB is all zeros
Every category except remove_corrupt_videos defaults on (each of those is deterministic and non-destructive to valid media). remove_corrupt_videos defaults off because zero-content detection is a heuristic; enable it deliberately, and even then it is applied only by the manual unarr library clean --apply — the daemon's automatic sweep never removes a suspect video. Set enabled = false to disable the automatic post-scan sweep — the manual unarr library clean command still works regardless.
unarr library stats reports the health, composition and quality of your Movies/TV library and download dir. It is a pure DRY-RUN — it only READS, never modifies anything on disk. Confined to the configured download/movies/tv directories.
unarr library stats # readable table (dry-run — reads only)
unarr library stats --json # emit the full stats struct as JSON (for scripts)
unarr library stats --workers 4 # limit concurrent ffprobe workers for the quality pass
Three blocks:
du — not apparent size) per category (Movies vs TV Shows vs Downloads) and the average size per title.unarr library clean performs, in dry-run: stubs, orphaned partials, duplicates, orphaned sidecars, empty dirs and media-named dirs — grouped by category with the total space reclaimable. Run unarr library clean --apply to actually free it.unknown and never aborts the report. Probing a large library end-to-end can take a while (bounded by --workers, default 8).--json emits the full LibraryStats struct (composition, health, quality) for piping into scripts.
Create a shell alias for shorter commands:
# Add to ~/.bashrc or ~/.zshrc
alias un=unarr
# Then use:
un search "breaking bad" --type show
un popular --limit 5
un start
| Flag | Description |
|---|---|
--json | Output as JSON (for piping to jq, scripts) |
--no-color | Disable colored output |
--api-key | API key (overrides config file and env) |
--config | Custom config file path |
All query commands support --json for scripting:
# Pipe to jq
unarr search "matrix" --json | jq '.results[].title'
# Save to file
unarr popular --json > popular.json
# Use in scripts
SEEDS=$(unarr search "inception" --json | jq '.results[0].torrents[0].seeders')
Location: ~/.config/unarr/config.toml
[auth]
api_key = "tc_your_api_key_here"
api_url = "https://unarr.app"
[agent]
id = "auto-generated-uuid"
name = "My PC"
[downloads]
dir = "~/Media"
# Ordered download-method preference. The web honours this list, so anything NOT
# listed is disabled — e.g. ["debrid"] means debrid-only and never falls back to
# torrent; ["debrid","usenet"] tries debrid then usenet. Omit (or use ["auto"])
# to let the server decide (default: auto — if you have debrid configured, cached
# titles use debrid automatically, otherwise torrent). Debrid/usenet must be
# configured in your TorrentClaw account — the agent only fetches links the web
# resolves. Requires unarr >= 1.1.5-beta.
preferred_methods = ["auto"] # e.g. ["debrid"], ["debrid","usenet"], or ["auto"]
# preferred_method = "auto" # legacy single value (still supported; superseded by preferred_methods)
max_concurrent = 3
max_download_speed = "0" # e.g. "10MB", "500KB", "0" = unlimited
max_upload_speed = "0"
# Read-only WebDAV export of your organized library (Movies / TV Shows) so
# Infuse, Kodi, VLC, etc. can browse and play it without the web player. Opt-in,
# off by default. Only GET/HEAD/PROPFIND are served — every write verb is 405'd.
# Basic auth: username defaults to "unarr"; password defaults to a stable value
# derived from your API key (shown by `unarr status`) unless you set one. The
# mount is served on the same port as streaming, at /dav/. Requires unarr >= 1.5.0-beta.
webdav_enabled = false
# webdav_username = "unarr"
# webdav_password = "" # blank = derive from the API key
# The mount answers only your local network (LAN, Tailscale, loopback);
# everything else gets a 404, so it stays private even when the stream port is
# reachable from the internet. Turning this on removes that restriction, and the
# Basic auth left protecting it has NO rate limiting — an exposed mount is an
# unthrottled password-guessing target. Remote playback does not use WebDAV, so
# leaving it off costs you nothing there.
# webdav_allow_wan = false
# Publish the HTTPS streaming port to your router (UPnP/NAT-PMP) so remote
# playback uses your agent's stable direct-TLS address instead of a CloudFlare
# quick tunnel, which changes hostname on every restart and gets rate-limited.
# On by default: that listener is TLS-only and every request needs a token the
# web mints, so nothing unauthenticated is reachable. The lease is renewed
# automatically and removed on shutdown. Set to false to never touch your router.
# auto_https_upnp = true
# Play Usenet (NZB) releases live from the news server instead of downloading the
# whole file first. On by default: "Play from Usenet" starts in seconds and the
# file is NOT kept in your library. A release that cannot stream (compressed or
# password-protected RAR…) falls back to a normal download automatically. Set to
# false to always download first. Configs saved by older versions carry an
# explicit `false` nobody chose; the first start after upgrading flips it to true
# once and records that in the file as `usenet_streaming_default_applied = true`
# (keep that line — it is what makes a later `false` stick). Changing it needs a
# daemon restart.
# usenet_streaming = true
[organize]
enabled = true
movies_dir = "~/Media/Movies"
tv_shows_dir = "~/Media/TV Shows"
# Folder/file layout for NEW downloads (existing files are never moved):
# "default" Movie (2000)/Movie (2000).mkv · Show/Season 01/Show - S01E01.mkv
# "plex" adds {imdb-tt…}, the show's year and episode titles — best for
# Plex, Infuse and Emby (aliases: "infuse", "emby"), keeps
# same-titled shows apart: One Piece (1999) {imdb-tt0388629}
# "jellyfin" same, with [imdbid-tt…]
naming = "default"
# Optional FileBot-style templates overriding the preset. Tokens: {n} title,
# {y} year, {imdbid} {tmdbid} {tvdbid}, {collection}, {s00e00} {sxe} {s} {e}
# {s00} {e00} {e.pad(4)}, {t} episode title, {vf} 1080p. "/" = folder,
# <…> = dropped when a value inside is missing, other braces are literal.
# Preview with `unarr organize preview`; restart the daemon after changing.
# movie_format = "{n}< ({y})>< {imdb-{imdbid}}>/{n}< ({y})>< {imdb-{imdbid}}>"
# series_format = "{n}< ({y})>< {tvdb-{tvdbid}}>/Season {s00}/{n} - {s00e00}< - {t}>"
# Library-hygiene sweep — runs automatically after each auto-scan, and manually
# via `unarr library clean`. Removes only deterministic junk; a valid video
# (>= min_video_bytes) is NEVER removed. See the "Library clean" section above.
[library.cleanup]
enabled = true # run automatically after each auto-scan
min_video_bytes = "1MiB" # anti-stub floor; a video below this is a stub
remove_stubs = true # delete videos below min_video_bytes
remove_orphan_partials = true # delete .part/.!qB/.aria2/.tmp/.partial with no active task
dedup_exact = true # collapse byte-identical duplicate videos (keeps one canonical copy)
remove_orphan_subtitles = true # delete sidecars with no owning video (.unarr checks the parent dir)
prune_empty_dirs = true # remove video-less dirs and media-named dirs
[daemon]
auto_upgrade = true # apply server-flagged upgrades in-place (since 0.9.6)
status_interval = "3s" # how often a running download reports progress
# Log file (unarr.log in the data dir). Rotation is OPT-IN and off by default:
# read the limitations in the "Logs" section above before turning it on. With 0
# the log grows until `unarr clean` or an external logrotate trims it.
log_max_size_mb = 0 # >0 = rotate unarr.log once it reaches this many MB; 0 = never rotate
log_max_files = 3 # rotated copies to keep (unarr.log.1 … .3)
log_level = "info" # default minimum severity for `unarr logs`: debug|info|warn|error
log_format = "text" # `unarr logs` output: "text" (verbatim) or "json" (json-lines)
[notifications]
enabled = true
# Read by the desktop companion (tray app), not the daemon: which player the
# web's Play button opens. Empty = autodetect. See [desktop] below.
[desktop]
player = ""
# player_command = "flatpak run org.videolan.VLC --start-time={start} -- {url}"
[general]
country = "US"
The in-browser player on unarr.app streams from the daemon over HLS (HTTP fragments + ffmpeg transcode for codecs the browser can't decode natively). Enabled by default — a fresh install "just works" without editing the TOML.
[downloads.transcode]
enabled = true # master switch
hw_accel = "auto" # auto | none | nvenc | qsv | vaapi | videotoolbox
preset = "veryfast" # libx264 preset
video_bitrate = "" # e.g. "5M" caps -b:v; empty = engine fallback (5M)
audio_bitrate = "192k" # e.g. "128k", "192k", "256k"
max_height = 0 # 0 = no cap; e.g. 720 forces 720p max
max_concurrent = 2 # max simultaneous ffmpeg processes
[downloads.transcode]| Key | Type | Default | Notes |
|---|---|---|---|
enabled | bool | true | Real-time HLS transcoding when source codec is browser-incompatible (HEVC, AV1, AC3, DTS). Requires ffmpeg + ffprobe on PATH. |
hw_accel | string | "auto" | Hardware accel: "auto", "none", "nvenc" (NVIDIA), "qsv" (Intel), "vaapi" (Linux), "videotoolbox" (macOS). |
preset | string | "veryfast" | libx264 preset. Slower preset = smaller files but higher CPU. Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow. |
video_bitrate | string | "" | E.g. "5M" caps -b:v. Empty falls back to the engine default (5M). |
audio_bitrate | string | "192k" | E.g. "128k", "256k". |
max_height | int | 0 | 0 = no cap. E.g. 720 forces 720p max — useful on weak GPUs. |
max_concurrent | int | 2 | Max simultaneous ffmpeg processes. Increase if hosting multiple users on a beefy box. |
If transcode.enabled = true but ffmpeg / ffprobe aren't on PATH, the
daemon logs a warning at startup and HLS sessions are rejected at runtime
with a clear error — install ffmpeg or set enabled = false.
[downloads.hls_cache] — persistent HLS segment cache[downloads.hls_cache]
enabled = true # on by default
size_gb = 5 # disk budget; LRU eviction once exceeded
dir = "" # custom path; empty = ~/.cache/unarr/hls-cache
| Key | Type | Default | Notes |
|---|---|---|---|
enabled | bool | true | Persists finished HLS encodes per (source, quality, audio_index). A second play of the same file at the same quality reuses the segments — no ffmpeg, near-zero CPU, instant playback. Set to false to delete segments on session close (original behavior). |
size_gb | int | 5 | Cache budget in gigabytes. When exceeded the LRU sweeper evicts the least-recently-used cached encodes hourly. Minimum 1 GB (smaller values are clamped up). |
dir | string | "" | Custom storage path. Empty defaults to ~/.cache/unarr/hls-cache (Linux/macOS) or the user cache dir (Windows). |
What it does. First play encodes normally (ffmpeg writes segments).
On session close, if every segment is on disk and ffmpeg exited cleanly,
the directory is sealed with a .complete marker and kept. Next time the
same source + quality combo is requested, the daemon serves segments
straight from disk — no transcode, no warm-up, no CPU cost.
Why per (source, quality, audio). Renaming the file or switching quality invalidates the entry: the segments are tied to the exact source bytes and the exact ffmpeg parameters. Re-encoding generates a new key.
Eviction. A background goroutine wakes every hour. If total cache size
exceeds size_gb, it deletes the oldest entries (by mtime) until under
budget. Active sessions are pinned — they never get evicted mid-play.
Disable. Either edit the TOML to set enabled = false, or remove the
cache directory manually (it'll be recreated as needed). Disabling does
not delete existing cached segments — drop dir (or ~/.cache/unarr/hls-cache)
to reclaim the space.
[downloads.vpn]| Key | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Managed VPN: at startup the daemon fetches a WireGuard config from your account and split-tunnels torrent traffic through it. Needs a PRO+ plan with the VPN add-on. Toggle with unarr vpn enable / disable. |
config_file | string | "" | Self-hosted / personal VPN: path to a local WireGuard .conf. Takes precedence over enabled — when set, the daemon uses this file and never calls the API. |
See the VPN section above for how it works (split-tunnel, no root) and how to protect your other devices.
[downloads.funnel] — public HTTPS hostname for the daemon (CloudFlare Quick Tunnel)[downloads.funnel]
enabled = false # off by default
| Key | Type | Default | Notes |
|---|---|---|---|
enabled | bool | false | Spawns cloudflared tunnel --url http://localhost:<stream_port> as a child process at daemon startup. Toggle with unarr funnel on / off. Requires cloudflared on PATH. |
What it does. Without a tunnel, the daemon is reachable on localhost,
your LAN, and (if installed) Tailscale. That covers the same-machine and
Tailscale-connected cases, but the browser-based player on unarr.app
fails on any other network because HTTPS pages can't fetch HTTP resources
("mixed content"). Enabling the funnel gives the daemon a public
https://<random>.trycloudflare.com hostname so the web player picks it up
and playback works from anywhere — phone on cellular, friend's laptop on a
foreign Wi-Fi, anywhere. The Stremio addon already works cross-network
(native mpv/VLC players ignore CORS), so this is strictly a web-player fix.
Privacy posture. Bytes pass through CloudFlare's edge — TorrentClaw never relays content (we don't see your traffic), CloudFlare does. Quick Tunnels are anonymous (no CF account required); the registration is unauthenticated and the hostname is a random label, but CF logs request metadata like any CDN would. If you want zero third-party byte access, use Tailscale instead.
Limitations (free Quick Tunnels).
| Aspect | Limit |
|---|---|
| Session lifetime | ~6 hours, then the hostname rotates. cloudflared re-registers automatically; the web picks up the new URL on the next sync. In-flight HLS sessions break across the rotation (browser retries). |
| Bandwidth | No documented hard cap, but CF reserves the right to throttle. 1080p HLS (~6 Mbps) is fine; 4K HEVC at 25 Mbps may hit throttling. |
| Latency | +20–80 ms vs direct LAN/Tailscale (extra hop browser → CF edge → tunnel). HLS player buffer absorbs it. |
| Concurrency | One tunnel serves N viewers. CF rate-limits ~200 req/s, plenty for HLS segments. |
| TOS | CloudFlare flags Quick Tunnels as "not for production traffic". They can decommission an abusive tunnel without notice. |
For heavy / high-throughput / persistent-URL use cases, switch to a CloudFlare Named Tunnel (free, needs a CF account) or run your own reverse proxy — both out of scope for the bundled command.
Disable. unarr funnel off flips enabled to false in the TOML and
prompts you to restart the daemon. You can also edit config.toml directly:
[downloads.funnel]
enabled = false
Install cloudflared.
apt install cloudflared (after adding CF's apt repo) — see
https://pkg.cloudflare.com. Or pull the static binary from
https://github.com/cloudflare/cloudflared/releases.brew install cloudflared.winget install --id Cloudflare.cloudflared.If cloudflared is not on PATH the daemon logs a warning at startup and
falls back to LAN/Tailscale-only reachability.
[desktop] — which player Play opensRead by the unarr desktop companion (the tray app that registers the
unarr:// handler), not by the daemon. Pressing Play on the web sends an
unarr://play link to it, and this section decides what opens.
[desktop]
# Empty = autodetect. Or: "mpv", "vlc", "iina" (macOS), "mpc" (Windows),
# "system" (whatever your OS opens video with).
player = ""
# An explicit command line, for players none of those names reach.
# Wins over `player`. See below.
# player_command = "flatpak run org.videolan.VLC --start-time={start} -- {url}"
| Key | Type | Default | Notes |
|---|---|---|---|
player | string | "" | "" = autodetect. "mpv" also resolves Celluloid (it embeds mpv and installs no mpv binary). "system" uses your OS default video app. A player that isn't installed falls back to autodetect and notifies you — the setting is never silently ignored. Watching in the browser is not a value here: that's the web's own "Web player" button. |
player_command | string | "" | Explicit command line with placeholders (below). Outranks player; while it is set, the tray's Player submenu is shown disabled. |
How the player is chosen, most specific first:
player_command — your own command lineplayer — a name from the table aboveStep 5 is a fallback, not a choice: if you want to watch in the browser, the web has its own "Web player" button, and going through the desktop app to get there would be a second route to the same place. Steps 1 and 4 exist because a Flatpak/Snap/AppImage install exposes no binary on PATH, and mpv.net or SMPlayer are their own spellings. Step 4 asks the system directly (freedesktop MIME association on Linux, LaunchServices on macOS, the registered file association on Windows), so it finds those — but it can only pass a URL, so playback starts from the beginning. Steps 2 and 3 know the player's flags and can pass resume position, title and language preferences.
player_command placeholders. The template is split into arguments here
and executed directly — never through a shell — so quoting is the only shell
syntax that applies. An argument whose placeholder has no value for a given
stream is dropped whole, so --start={start} simply disappears when there is
nothing to resume.
| Placeholder | Value |
|---|---|
{url} | The stream URL. Appended automatically if the template never mentions it. |
{web} | The unarr web player page for this stream (falls back to {url}). |
{start} | Resume position in seconds. Absent when starting from the beginning. |
{title} | Display title for the player window/OSD. |
{alang} / {slang} | Preferred audio / subtitle languages, comma-separated. |
{subfile} | URL of an external subtitle file to side-load. First one only — see the note below. |
{subfile}carries a single subtitle. unarr can side-load several external subtitle files (AI translations and shared provider subtitles), but a placeholder always substitutes inside ONE argument, so a template cannot emit a repeatable flag more than once.{subfile}therefore expands to the first subtitle only, and the argument disappears when there is none. The built-in players (mpv, Celluloid, VLC) receive all of them — use one of those if you want the full subtitle menu.
# Flatpak VLC (no `vlc` on PATH)
player_command = "flatpak run org.videolan.VLC --start-time={start} -- {url}"
# mpv.net on Windows
player_command = 'C:\Program Files\mpv.net\mpvnet.exe --start={start} --force-media-title={title} -- {url}'
# SMPlayer (its own flag spellings)
player_command = "smplayer -media-title {title} -start {start} {url}"
Environment variables override config file values:
export UNARR_API_KEY=tc_your_api_key
export UNARR_API_URL=https://unarr.app
export UNARR_COUNTRY=ES
export UNARR_DOWNLOAD_DIR=~/Media
# Desktop companion — one-off override without editing the TOML
export UNARR_DESKTOP_PLAYER=vlc
export UNARR_DESKTOP_PLAYER_COMMAND="flatpak run org.videolan.VLC -- {url}"
Speed limits use human-readable format:
max_download_speed = "10MB" # 10 megabytes/sec
max_upload_speed = "1MB" # 1 megabyte/sec
max_download_speed = "500KB" # 500 kilobytes/sec
max_download_speed = "0" # unlimited (default)
The agent reports a small set of lifecycle events to the server — enough to see why an agent that connected never came back:
No file names, library contents, search terms, or download activity are sent — only these lifecycle signals plus the agent's version and OS. It helps us fix the setup problems that make agents fail on the first run.
To turn it off completely:
[telemetry]
enabled = false
or with an environment variable (wins over the config file — handy for headless/Docker):
UNARR_TELEMETRY=off unarr start
When disabled the agent sends nothing — it still registers, syncs, and downloads exactly the same. Telemetry is purely additive.
unarr mount exposes completed Real-Debrid / AllDebrid / TorBox / Torrin files and compatible
NZBs as a local folder, reading media on demand. This is disabled by default
and independent of the existing WebDAV export of downloaded files. Enable
the mount with unarr mount and connect providers on the Unarr website. unarr
prepares rclone automatically, explains missing filesystem drivers before asking
permission to install them, and creates a default mount folder. The normal agent
service owns the mount after the command exits and retries it after transient
failure or login/reboot. Run unarr init first to configure the normal agent.
The command confirms saved settings and an activation request; check the folder
and agent logs to verify it is mounted. Use unarr umount to disable it,
unarr config mount for advanced local settings, or unarr mount <directory>
to choose a destination. On the website, Add to local Usenet folder sends
only the selected NZB to the agent's managed inbox; manual NZB copies remain
supported.
Persistent mount/umount use the single default agent configuration; custom
--config and shell-only overrides are refused before login or service changes.
Use unarr mount serve --config /path/to/config.toml for a separately managed
WebDAV process. Disabling the mount leaves an intentionally stopped agent stopped.
See setup, configuration and limitations and the performance report.
Generate tab-completion scripts for your shell:
# Bash — add to ~/.bashrc
eval "$(unarr completion bash)"
# Zsh — add to ~/.zshrc
eval "$(unarr completion zsh)"
# Fish
unarr completion fish > ~/.config/fish/completions/unarr.fish
# PowerShell — add to $PROFILE
unarr completion powershell >> $PROFILE
Completions provide tab-completion for commands, flags, and flag values (e.g. --type <Tab> shows movie and show).
Walk a folder recursively, analyze each video file with ffprobe, and sync quality data to your account.
unarr scan ~/Media # scan default download dir
unarr scan /mnt/nas/Movies # scan a specific path
unarr scan ~/Media --status # show last scan results without re-scanning
unarr scan ~/Media --workers 4 # use 4 parallel ffprobe workers
unarr scan ~/Media --no-sync # analyze locally without uploading results
The daemon also runs an automatic background scan when it detects new files in the download directory.
Mirrors are alternate base URLs the agent falls back to when the primary domain is unreachable — useful for bypassing DNS blocks, ISP filters, or short-lived outages without restarting the agent.
unarr mirrors list # show currently configured mirrors
unarr mirrors update # refresh from the server's canonical list
unarr mirrors test # probe every configured mirror for latency and reachability
These commands are planned for future releases:
| Command | Description |
|---|---|
unarr moreseed | Find same quality with more seeders |
unarr compare | Compare two torrents side by side |
unarr monitor | Watch for new episodes of a series |
unarr open | Open content in the browser |
See CONTRIBUTING.md for development setup, code style, and guidelines.
MIT License — see LICENSE for details.
unarr backup prune --pre-restorepruneconfig.tomlunarr login