Sailing the digital seas with ease,
Fetching treasures as we please.
With *arr at helm and put.io's might,
Downloads flow through day and night.
plundrio (/หplสndriหoส/) is a put.io download client designed to seamlessly
integrate with the *arr stack (Sonarr, Radarr, Lidarr, etc.). Files are
automatically added to put.io and downloaded to the local disk once complete.
By using plundrio, you can benefit from faster downloads if the file is already cached by put.io, allowing you to download it locally immediately. This is especially useful if you have a low bandwidth connection, as you can easily saturate it by downloading from put.io instead of the original source. Additionally, if put.io already has the file cached, you can skip the initial download step from the origin to put.io, saving time and bandwidth. However, in all other cases, a direct download from the origin may be more beneficial, as put.io essentially performs the same download process.
- ๐ Features
- ๐ง How It Works
- ๐ Prerequisites
- ๐ฆ Installation
- ๐ Getting Started
- โ๏ธ Configuration
- ๐ Configuring *arr Applications
- ๐ฎ Commands
- ๐ก Tips & Optimization
- ๐ Troubleshooting
- โ Frequently Asked Questions
- ๐ค Contributing
- ๐ License
- ๐ Seamless integration with Sonarr, Radarr, and other *arr applications supporting Transmission RPC
- ๐ Minimal local state; multiple instances per put.io account supported, one per download directory
- โก Fast and efficient downloads from put.io (with resume support)
- ๐ Parallel downloads with configurable worker count to maximize bandwidth
- ๐งน Automatic cleanup of completed transfers
- ๐ Secure OAuth token handling for put.io authentication
- ๐ Comprehensive transfer logging with detailed metadata for all transfers
- ๐ Automatic retry of failed transfers with configurable retry attempts
plundrio makes downloading from put.io simple and automatic:
graph LR
A[*arr Application] -->|Sends download request| B[plundrio]
B -->|Adds transfer to| C[put.io]
C -->|Transfer completes| D[plundrio monitors]
D -->|Downloads files| E[Local Storage]
D -->|Cleans up| C
- Your *arr application sends a download request to plundrio via Transmission RPC
- plundrio forwards this request to put.io
- plundrio tracks all put.io transfers pointing at the specified target directory
- Once a transfer completes, it automatically downloads all files to your local folder
- Downloads are parallelized with multiple workers to optimize speed
- Transfers and their files are cleaned up when all files are present locally and the transfer finished seeding
plundrio implements a specialized state tracking system to ensure seamless integration with *arr applications:
-
Transfer Record Preservation:
- When a transfer completes and files are downloaded, plundrio removes the files from put.io but keeps the transfer record
- This transfer record acts as a central entity that *arr applications can query to determine completion status
- Transfers are only fully removed when explicitly requested via torrent-remove RPC call
-
Progress Calculation:
- put.io download progress (0-100%) is mapped to 0-50% of the total progress
- Local download progress (0-100%) is mapped to 50-100% of the total progress
- For transfers being processed: progress = (put.io_progress / 2) + (local_progress * 0.5)
- For completed transfers: progress = 100% with "seeding" status
- This two-phase progress tracking gives *arr applications accurate visibility into both remote and local download status
-
Completed-transfer removal:
- Only locally processed transfers report that their per-torrent idle limit has been reached, allowing *arr to request removal after import.
seedRatioMode: 2reports unlimited seeding and overrides the ratio goal configured in *arr, because put.io owns seeding. Plundrio uses the idle-limit fields to signal removability instead.
This approach ensures reliable integration with *arr applications while optimizing put.io storage usage.
Before installing plundrio, ensure you have:
- put.io Account: An active put.io subscription
- OAuth Token: A valid put.io OAuth token (instructions for obtaining below)
- Storage Space: Sufficient disk space for your downloads
- Network Bandwidth: Adequate bandwidth for parallel downloads
- *One or more arr Applications: Sonarr, Radarr, Lidarr, etc. (optional, but recommended)
go install github.com/elsbrock/plundrio/cmd/plundrio@latestplundrio can be integrated directly into your NixOS configuration as a service:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
plundrio.url = "github:elsbrock/plundrio";
};
outputs = { self, nixpkgs, plundrio, ... }: {
nixosConfigurations.mySystem = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
plundrio.nixosModules.default
{
services.plundrio = {
enable = true;
targetDir = "/var/lib/plundrio/downloads";
putioFolder = "plundrio";
authTokenFile = "/run/credentials/plundrio.service/token";
# Optional configurations with defaults:
# listenAddr = ":9091";
# workerCount = 4;
# logLevel = "info";
# user = "plundrio";
# group = "plundrio";
};
}
];
};
};
}The NixOS module:
- Creates a dedicated system user and group (plundrio)
- Sets up a systemd service with proper security hardening
- Creates the target directory with appropriate permissions
- Automatically starts at boot and restarts on failure
docker run --rm -it ghcr.io/elsbrock/plundrio:latest -- --helpMake sure to expose the transmission RPC port (default 9091) and mount the download directory:
docker run -d \
--name plundrio \
-p 9091:9091 \
-v /path/to/downloads:/downloads \
-e PLDR_TOKEN=your-token \
-e PLDR_TARGET=/downloads \
-e PLDR_FOLDER=plundrio \
ghcr.io/elsbrock/plundrio:latestThe Docker image is available for both x86_64 and ARM64 architectures. Docker will automatically pull the correct version for your platform.
Download the latest binary package for your platform (x86_64-linux or aarch64-linux) from the releases page. The multi-arch Docker image is available on GHCR (see Using Docker).
plundrio get-tokenThis will guide you through the OAuth authentication process and provide you with a token.
plundrio generate-configThis creates a template configuration file that you can customize.
Edit your configuration file or set environment variables to specify your download directory and put.io folder.
plundrio run \
--target /path/to/downloads \
--folder "plundrio" \
--token YOUR_PUTIO_TOKEN \
--workers 4Add plundrio as a Transmission download client in your *arr application (see Configuring *arr Applications below).
plundrio supports multiple configuration methods:
- Config file (YAML format):
target: /path/to/downloads # Target directory for downloads
folder: "plundrio" # Folder name on put.io
token: "" # Put.io OAuth token (prefer env var)
listen: ":9091" # Transmission RPC server address
workers: 4 # Number of download workers
use-categories-target: false # Put local downloads into per-category subfolders (e.g. <target>/tv)
use-categories-putio: false # Create per-category subfolders on put.io (e.g. <folder>/tv)
stalled-transfer-timeout: 6h # Report unchanged Put.io downloads after this duration (0 disables)
download_start_window: # Optional local download start window
enabled: false
start: "23:00"
end: "05:00"
log_level: "info" # Log level (trace,debug,info,warn,error,fatal,panic,none,pretty)stalled-transfer-timeout tracks the downloaded byte count of each Put.io transfer. The default is 6h; set it to 0 to disable detection. Unchanged bytes in DOWNLOADING produce Transmission error 3 (TR_STAT_LOCAL_ERROR). This deliberately lets Sonarr/Radarr treat the transfer as a failed download: depending on their failed-download handling settings, they may blocklist the release, grab a replacement, and request torrent-remove, which deletes the original remote transfer and may delete local data. Plundrio's stall detector itself only reports the error. Completed-transfer removal through the seed-policy fields is separately limited to locally processed transfers.
A temporary move to COMPLETING preserves the no-progress baseline, but stall errors are reported only in DOWNLOADING. Any change in the byte count resets the baseline, including during COMPLETING; completion, other inactive states, and disappearance from the monitored list discard it. Restarting plundrio establishes a fresh observation window. Detection may occur up to one polling interval after the timeout.
download_start_window only gates when plundrio may begin a new local download. It does not stop Put.io transfers from being created, and it does not interrupt downloads that are already in progress.
By default plundrio places every download directly in the target directory and adds every transfer to the configured put.io folder, ignoring the category your *arr app requests via the Transmission download-dir argument (e.g. /downloads/tv). Two independent, opt-in flags change this:
use-categories-target(PLDR_USE_CATEGORIES_TARGET): local downloads are placed into a per-category subfolder of the target directory. Withtarget: /downloadsand an *arr download path of/downloads/tv, files land in/downloads/tv.use-categories-putio(PLDR_USE_CATEGORIES_PUTIO): plundrio creates a subfolder named after the category under the configured put.io folder and uploads the transfer into it (e.g.<folder>/tv). plundrio still monitors transfers placed in these subfolders; empty category folders are left in place. Only a single level of category subfolders is supported on put.io.
The two flags are independent โ enable either or both. Both default to false, preserving the previous flat behavior.
-
Command-line flags (see full list with
plundrio run --help) -
Environment variables (prefixed with
PLDR_):
export PLDR_TARGET=/path/to/downloads
export PLDR_TOKEN=your-putio-token
export PLDR_FOLDER=plundrio
export PLDR_LISTEN=:9091
export PLDR_WORKERS=4
export PLDR_USE_CATEGORIES_TARGET=true
export PLDR_USE_CATEGORIES_PUTIO=true
export PLDR_STALLED_TRANSFER_TIMEOUT=6h
export PLDR_DOWNLOAD_START_WINDOW_ENABLED=true
export PLDR_DOWNLOAD_START_WINDOW_START=23:00
export PLDR_DOWNLOAD_START_WINDOW_END=05:00
export PLDR_LOG_LEVEL=infoConfiguration values are loaded in the following order, with later sources overriding earlier ones:
- Default values
- Configuration file
- Environment variables
- Command-line flags
๐ก Security Note: Store OAuth tokens in environment variables rather than config files or command-line arguments for better security.
To add plundrio to your *arr application (Sonarr, Radarr, etc.):
- Go to Settings > Download Clients
- Click the + button to add a new client
- Select "Transmission" from the list
- Fill in the following details:
- Name: plundrio (or any name you prefer)
- Host: localhost (or your server IP)
- Port: 9091 (or your configured port)
- Use SSL: leave unchecked
- URL Base (if shown): keep default value of
/transmission/ - Username: leave empty
- Password: leave empty
- Category: keep default value
- Click "Test" to verify the connection
- Save if the test is successful
plundrio will now automatically handle downloads from your *arr application through put.io.
plundrio run \
--target /path/to/downloads \
--folder "plundrio" \
--token YOUR_PUTIO_TOKEN \
--workers 4reconcile report performs a read-only comparison of the configured Put.io
folder and local download root against current transfer records:
plundrio reconcile report \
--target /path/to/downloads \
--folder plundrioThe command emits stable JSON with separate active and unmanaged arrays.
Put.io objects use IDs such as putio:12345; local objects use deterministic
IDs derived from their root-relative paths, current filesystem identity, and
SHA-256 content fingerprints. Unmanaged regular files are read in full, so
reporting large unmanaged trees adds disk I/O; active payloads are not read.
Different content invalidates its prior ID even when a filesystem reuses the
same inode and timestamps. Regenerate reports made by older metadata-only
versions before selecting local IDs. Nested roots are
reduced to the smallest non-overlapping objects: active descendants and their
unmanaged siblings are reported separately, while a wholly unmanaged directory
is one object whose size includes its contents. The command never creates a
missing Put.io folder and does not move, rename, download, or delete anything.
Use the same --use-categories-putio setting (or config/environment value)
as run: ownership includes the configured folder and, when enabled, its
direct category folders only. Deeper transfers are outside the monitor's scope.
Persisted categories protect both bare and category-local active paths, even
when category state is stale. The root's .plundrio-state.json and
.plundrio-files/ are reserved internal state and are never cleanup candidates.
Pending-removal manifests also protect their local transfer directories even
when the remote record moved outside managed folders or disappeared while a
worker drains. If a pending removal has missing or malformed ownership evidence,
reconciliation fails closed; resolve that removal before selecting cleanup IDs.
An unmanaged object has no matching current transfer; that alone does not prove
it was imported or is disposable. Review the report and verify retained copies
before selecting IDs.
Pass exact IDs from a current report and explicitly select the roots in scope.
Deletion is a dry run unless --apply is also present:
plundrio reconcile delete \
--target /path/to/downloads \
--folder plundrio \
--putio \
--id putio:12345
plundrio reconcile delete \
--target /path/to/downloads \
--folder plundrio \
--local \
--id local:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
--apply--putio and --local are independent and may be combined only when both
roots are intended. Plundrio inventories the roots once per batch, then refreshes
each selected branch and current transfer ownership before its applied deletion.
It refuses IDs that are active, missing, or no longer unmanaged. Unrelated remote
and local subtrees are not crawled again. Local removal rechecks the selected
tree's identity and contents through Go's root-confined filesystem API and rejects symlink
objects, parents, and special files. Each selected local tree is reread during
revalidation; ownership is checked again after the final content read. Local
deletion requires Unix filesystem identity and fails
closed on other platforms; read-only reports, dry runs, and Put.io deletion remain
available. No filesystem/API transaction can exclude a concurrent writer after
the final check; stop writers while applying a reviewed batch.
The JSON result records every selected object's path,
size, status, reason, and error; skipped or failed objects make the command exit
non-zero after the complete batch is reported.
plundrio generate-configplundrio get-token-
Trash Bin Management: We recommend turning off the trash bin in your put.io settings. This helps keep your put.io account clean and saves space. The trash cannot be deleted programmatically.
-
Download Speed Optimization: Downloads are optimized using the grab library for maximum efficiency. The default worker count of 4 allows for parallel downloads to maximize your available bandwidth.
-
Worker Count Tuning:
- For faster internet connections (100Mbps+), consider increasing worker count to 5-8
- For slower connections, reduce worker count to 2-3 to avoid bandwidth saturation
- Monitor system resource usage to find the optimal setting for your environment
-
Automatic Downloads: If you set the default download folder of put.io to the folder configured in plundrio, you can automatically download files added through other means (e.g., via chill.institute).
-
Security Best Practices:
- Use environment variables for sensitive data like OAuth tokens
- Consider using Docker secrets or a secure environment variable manager in production
- Regularly rotate your OAuth tokens for enhanced security
-
Connection Refused
- Ensure plundrio is running and the port (default 9091) is not blocked by a firewall
- Verify the correct host/IP is configured in your *arr application
-
Authentication Failures
- Regenerate your OAuth token using
plundrio get-token - Check that the token is correctly set in your configuration
- Regenerate your OAuth token using
-
Download Issues
- Verify your target directory is writable
- Check available disk space
- Ensure your put.io account is active and has the files available
-
Performance Problems
- Adjust worker count based on your bandwidth and system capabilities
- Check for network throttling or limitations
Can I use plundrio without *arr applications?
Yes, plundrio will monitor and download any transfers in your configured put.io folder, regardless of how they were added.
How does plundrio compare to the official put.io client?
plundrio focuses on automation and integration with *arr applications, while the official client offers a more general-purpose interface.
Can I run multiple instances of plundrio?
Yes, as long as each instance uses its own --target directory. plundrio keeps a small
amount of state there: .plundrio-state.json holds the put.io transfer ID โ category
mapping used by use-categories-target, and .plundrio-files/ holds per-transfer file
manifests, removal markers, and review holds (see below). Instances sharing a download
directory will overwrite each other's copies of this state.
Does plundrio support VPNs or proxies?
plundrio uses your system's network configuration. If your system routes through a VPN or proxy, plundrio will use that connection.
How can I monitor plundrio's status?
plundrio logs its activities to stdout. You can redirect these logs to a file or use a log management system.
After restart, a Put.io COMPLETED or SEEDING transfer initially reports
50% / downloading until Plundrio restores or verifies its local completion.
This changes the status existing *arr clients see on restart and prevents
premature import or removal while the local copy is still pending.
Upgrades can encounter old records whose source has gone but which have no
manifest: either source ID zero or a positive source ID returning NotFound.
Neither shape proves local completion. These records now enter NeedsReview,
not completed or failed: stopped, 50%, unknown ETA, zero transfer rates, and an
explanatory errorString without a download-failure signal. An unknown size uses
one byte remaining as a sentinel, not a measured payload size. No file list or
ownership is invented. Missing child folders during listing and invalid existing
manifests remain errors; they are not classified as legacy source absence.
The hold is persisted in .plundrio-files/<id>.review.json. Preserve these files:
restarts, source reappearance, and remote ERROR status do not resume processing
or authorize automatic deletion. Corrupt review markers also hold the record
and require state repair. If storage cannot persist a marker, the current
process remains held, but restart durability is not guaranteed; repair storage
before restarting, and allow a monitor poll to persist the hold after repair.
This is a safety change for zero-ID legacy records previously
assumed complete, as well as positive-ID records previously reported failed.
First verify the actual retained/imported copy in the consuming application; an old 100% display, matching directory name, or same-size file is not proof. If the copy is missing, recover or download it separately before retiring the old record. Do not fabricate a manifest or remove a review marker to force completion. A restored source or changed identity requires operator investigation; it is deliberately not an automatic resume/retirement path.
After verifying the retained copy, an operator may send this explicit extension
to the existing Transmission RPC endpoint, replacing 101 with the one exact
numeric transfer ID reviewed:
{"method":"torrent-remove","arguments":{"ids":[101],"delete-local-data":false,"plundrio-retire-reviewed":true,"plundrio-copy-verified":true}}This acknowledgment is an operator assertion, not machine proof. The server
rechecks the review marker, unchanged remote identity, completed remote status,
absent source root and absent manifest. It removes only the transfer record:
neither Put.io source deletion nor local deletion is called. Any failure retains
the durable hold; retry the same explicit request after resolving the error.
Ordinary torrent-remove is rejected for review holds, even after restart or
failed retirement. No batch IDs, hashes, missing acknowledgment, or local-delete
request is accepted. Retirement does not backfill ownership: preserved local
files may subsequently appear unmanaged to explicit reconciliation.
Ordinary removal of a remotely ready record also waits until its local state is classified; retry after the next monitor poll. This closes the initial listing/retirement race without disabling explicit cancellation of active downloads. Failed reviewed retirement remains a warning, not a download error.
.plundrio-files/ is reserved for transfer ownership manifests in the download
root. Preserve it across restarts and exclude it from media scans, filesystem
reconciliation, and unmanaged-file deletion. Transmission files names are
relative to downloadDir; bytesCompleted currently reports whole-transfer
completion (zero until complete, then each file's full length).
torrent-remove persists a removal marker before deleting remote data. Remote
transfer deletion gets at most three attempts per request. If it still fails,
the RPC returns an error, the torrent reports stopped with a removal error,
and local processing stays suspended across restarts. Local files are retained
on that failure even when delete-local-data was requested; repeat the request
after fixing Put.io access to finish removal.
Pending removals release active category, transfer, and retry tracking. Their
category and ownership manifest stay on disk under .plundrio-files/, without
a permanent in-memory tombstone cache. Metadata is removed after a successful
retry or after a successful full Put.io listing confirms the ID is absent and
any active local worker has drained. Moving a transfer outside the configured
folder does not count as deletion. Disk retention is bounded by surviving
remote records, not a time limit: during an API outage these safety records
remain. To resolve one manually, delete that exact transfer on Put.io and let
the next successful poll reclaim its metadata; do not delete marker files to
force a retry. An already-running file download may finish, but queued work,
new attempts, and source cleanup cannot restart the removed transfer.
Contributions to plundrio are welcome! Here's how you can contribute:
-
Code Contributions:
- Fork the repository
- Create a feature branch
- Make your changes
- Submit a pull request
-
Bug Reports:
- Open an issue describing the bug
- Include steps to reproduce
- Provide system information
-
Feature Requests:
- Open an issue describing the feature
- Explain the use case and benefits
-
Documentation:
- Help improve the README
- Add examples or tutorials
Please open an issue first to discuss what you would like to change for major features or changes.
This project is licensed under the MIT License - see the LICENSE file for details.