Releases: ptheofan/putiorr
Release list
v3.0.6
Changed
-
A put.io transfer putiorr cannot place is now ignored quietly. The
dashboard used to report it and tell you to give each RR profile its own
put.io folder — advice that contradicted the recommended setup, where every
profile shares one folder, and that asked for a whole put.io reorganisation
over a single stray transfer. The notice, its stored state and its per-poll
warning are gone.Nothing about what gets downloaded changed: a transfer whose folder maps to
exactly one enabled profile is still adopted, and one that does not is still
left alone on put.io — now without the noise. SetPUTIORR_LOG_LEVEL=debugif
you ever need to ask why a particular transfer was skipped.Installs carrying the old notice have it cleared on first start.
v3.0.5
Fixed
-
Sending a
.torrentfile to put.io always failed with a 401. put.io serves
uploads from a host of its own,upload.put.io, and putiorr posted them to
the API host instead. Magnet links were never affected — they go to a
different endpoint — so a working put.io connection would add magnets happily
and reject every.torrent, reporting a credentials error against a token
that was perfectly valid. Uploading metainfo had never worked by any route:
not from the browser extension, and not from an *arr sending base64 metainfo
overtorrent-add.The 401 was hard to read because put.io answers a rejected token with the same
sentence on every endpoint, so the message pointed at the credentials while
the credentials were fine.
v3.0.4
Security
-
Cross-site requests can no longer change putiorr's state. Only
POST /api/grabwas protected; every other state-changing/apiendpoint —
settings, profiles, download profiles, starting and deleting downloads, the
orphan routes, the OAuth routes andPOST /api/poll— could be driven by any
page the user happened to visit. No credentials were needed, because putiorr
has none to steal: the attacker only needed the victim's browser to reach a
putiorr on their network.A request is now refused when the browser reports it was started by another
site. Nothing else changes: the dashboard,curland scripts, the browser
extension, and the *arr apps on the Transmission RPC endpoints are all
unaffected, and reads are untouched. Refusals are logged with the path and
method behind them.This is a mitigation, not a boundary. A caller that is not a browser is
deliberately still allowed through — refusing those would break every
scripted setup and would not stop the attack it is aimed at.
v3.0.3
Fixed
-
Changing a profile's download folder no longer destroys its downloads.
Editing Shared download folder on a profile that owned downloads was
accepted silently, and the next poll then read every one of them as
user-deleted: it cancelled the put.io transfer, deleted the put.io file and
removed the row, leaving the files orphaned on disk. A download's path is
derived from its profile's current folder, so moving the folder pointed
putiorr at a location the files were never in.The change is now refused while the profile owns downloads, naming how many
and what to do instead — let them finish and leave putiorr, or delete them
from the dashboard. Nothing is moved on disk. Saving a profile without
touching the folder still works, and/downloadsand/downloads/count as
the same folder. The wizard offers to put the folder back so a refused value
cannot sit in the form blocking every later save.
v3.0.2
Changed
-
The database upgrade summary can now be dismissed.
The last database upgrade migrated N downloads. Files on disk were not touched.was written
once and never cleared, so it stayed on the dashboard for the life of the
database. It now carries a dismiss control, recorded on the server so it
survives a reload, a restart and a different browser. The migration reports
themselves are kept — they remain inGET /api/settingsas the record of the
run — and the dismissal is keyed to the upgrade it was read for, so a later
migration shows its own summary again.The quarantine warning beside it has no dismiss control and never will: those
downloads stay invisible until someone acts on them, so the panel stays up
for the warning even once the summary is gone.
v3.0.1
Fixed
- Empty legacy tables no longer raise a data-loss alarm. An install that had
briefly run an older putiorr — which a stale:latestpull does — was told
An older putiorr has written 0 downloads into storage this version cannot read, and pointed at restoring a.pre-downloads-*.bak. Nothing had been
written and nothing was missing; following the advice would have discarded
every download made since the upgrade. Starting an older putiorr is enough to
recreate the tables the 3.0.0 migration dropped, so their presence was never
evidence of anything. They are now dropped again on boot when they hold no
rows, and the warning is raised only when rows really are stranded — in which
case it names how many.
v3.0.0
A major release, because it changes how a download's owning profile is decided
and rewrites the database to enforce it. Existing setups need attention before
and after the upgrade; read the breaking changes first.
The short version: the category no longer picks a profile, ownership is decided
once when a download is created and never re-derived, and a browser extension
can now grab magnet links and .torrent files straight into putiorr.
Breaking changes
-
The download-client category no longer selects a profile. It names the
staging subfolder under the owning profile's download folder, and nothing
else — it cannot pick a profile and cannot veto one. If you used mapped
categories to steer grabs from a single app into different putiorr profiles,
that stops working: everything from that app now lands in the profile its
request resolved to. Give each destination its own RPC path and point the app
at the right one. If you used mapped categories the ordinary way — Prowlarr
labelling releases — nothing changes, and the User-Agent bypass they used to
need to get past the category check is gone with the check. -
On a multi-profile install, the shared
/transmission/rpcendpoint needs
the caller to identify itself. Sonarr, Radarr, Lidarr, Readarr and Prowlarr
put their own name in theUser-Agentand need no change. Anything else — a
script, a generic Transmission client — resolves to no profile, and
torrent-addandtorrent-removeare refused with each profile's RPC path
named in the refusal. The same applies to two profiles that answer to one
name, such as a second Sonarr. Action: give that client the RPC path of
the profile it means; the path always wins over the header.session-getand
torrent-getstill answer, so a connection test passes and existing
downloads keep importing, but new grabs stop until the path is set. -
The database is rewritten once, in place, on first start.
transfersand
transfer_associationscollapse into onedownloadstable, and
association_filesbecomesdownload_files. The upgrade runs automatically
in a single transaction, verifies foreign keys before committing, and rolls
back and refuses to start if anything is wrong. It writes a
VACUUM INTObackup —<state-file>.pre-downloads-<timestamp>.bak, beside
the state file, logged with its path — before it touches anything, and never
deletes it. Files on disk are never touched by the upgrade. Action:
rehearse it on a copy of your database first, and keep the backup. Restoring
that backup is the only way back to 2.0.x, because 2.0.x recreates the legacy
tables and writes into storage 3.0.0 does not read. -
Rows the upgrade cannot place are quarantined rather than guessed at. A
download with no owning profile, none identifying a put.io transfer, or a
second association to a transfer an older sibling already owns, moves to a
Needs attention list in the dashboard with a profile picker. Its files
stay exactly where they are. On a database with exactly one profile,
ownerless rows are adopted by it rather than quarantined. Action:
reconcile that list against the *arr queues before adding new downloads — an
*arr holding a quarantined download's Transmission id gets an empty
torrent-getuntil it is reassigned. Reassigning restores the original
Transmission id, so the queue item recovers. What the upgrade did is recorded
inGET /api/settingsunderschemaMigrations. -
Downloads with no owning profile are no longer adopted at boot. The
boot-time sweep that handed them to whichever profile sorted first is gone.
They appear under Needs attention instead, and the sweeps skip them. -
GET /api/downloadsanswers{ downloads, orphaned }instead of a bare
array. The WebSocket downloads payload carries the same two arrays.
Action: anything scripted against the old shape needs.downloads. -
DELETE /api/profiles/:idrefuses to delete a profile that owns downloads
until it is told what happens to them. SendreassignTo, or
deleteDownloads: truewith the optionaldeleteRemoteanddeleteLocal
flags — neither of which defaults to true. A profile that owns nothing still
deletes with no body. The dashboard asks with a dialog that shows the counts
first;GET /api/profiles/:id/deletion-previewis the same data. -
A disabled profile refuses new work by name instead of disappearing. Its
RPC path used to serve the dashboard's HTML with HTTP 200, which every *arr
reads as a successful grab. It now answers with a refusal naming the profile,
it still claims its browser sites, and it is still counted when the shared
endpoint decides whether it is ambiguous. Existing downloads keep running.
Action: if you were disabling a profile to free up the shared endpoint or
release a site, delete it instead. -
Putiorr Grab profiles have no Transmission RPC endpoint. The
/grab/<slug>/rpcpath only ever existed because the column wasNOT NULL UNIQUE; it now answers every request with a refusal. Browser grabs reach a
grab profile throughPOST /api/grab. Nothing to do — no download client
connects to a grab profile. -
Every RR profile must have a download profile. The upgrade assigns the
default to any profile that had none. A download profile still in use can no
longer be orphaned:DELETE /api/download-profiles/:idmoves the profiles
referencing it to the default. -
Grab profiles created through
POST /api/profilesor
PUTIORR_PROFILES_JSONnow default to auto-removing completed downloads,
matching what the wizard and the documentation already claimed. Nothing
imports a browser grab, so the finished transfer is dropped from putiorr and
from put.io while the files stay on disk. Action: send
auto_remove_completed: falseto keep the old behaviour. -
The
rpc request failedlog line namesprofileswhere it named
enabledProfiles, and each entry carriesenabled. Action: anything
scraping that line needs the new key.
Added
- A Chrome browser extension (
extension/, Manifest V3, loaded unpacked)
that capturesmagnet:links,.torrentdownloads, and links carrying a
magnet in their query on any site, and sends them to putiorr, which adds them
to put.io and downloads them locally..torrentfiles are fetched from
inside the page, so private-tracker session cookies apply. Right-click →
Send to putiorr → profile overrides the profile for one grab and covers
trackers whose download URLs do not end in.torrent. Every grab reports
itself on the page and again as a Chrome notification. Install instructions
are in the extension guide. - A Putiorr Grab profile preset. Browser grabs get their own preset instead
of borrowing the *arr ones: the wizard drops the RPC endpoint step entirely,
shows the fields a grab actually needs, and turns auto-remove on by default. - Per-profile browser sites. Which site routes to which profile is
configured in putiorr, on the profile, not in the extension. A plain entry
matches that host exactly;*.x.examplematches the domain and every
subdomain, longest base wins. One profile may additionally be set to take
every site no other profile claims; without one, an unclaimed grab is refused
rather than guessed at. POST /api/grab— server-side profile resolution, bencode-validated
uploads, and anX-Putiorr-Grabheader required as an anti-CSRF measure.POST /api/profiles/:id/browser-sites— appends one site to one grab
profile, which is what the extension's toolbar popup writes when it claims the
current page's site.- A Needs attention section in the dashboard for downloads no profile owns,
with a profile picker and a delete that says what it will remove. - A profile deletion dialog that measures the downloads off disk and states
what each answer would do, plusGET /api/profiles/:id/deletion-preview. schemaMigrationsinGET /api/settings— what each schema upgrade
migrated, adopted, and quarantined, and whether legacy tables have reappeared
because an older putiorr wrote to the database.
Fixed
Several of these were silent data loss.
- A put.io rename deleted the download and cancelled its put.io transfer.
The sweep looked under the new name, found nothing, read that as the user
deleting the files, and destroyed the download while its files sat orphaned
at the old path. A download's staging folder is now frozen the first time it
is staged, and already-staged rows are backfilled. - put.io folder listings only ever read the first page. Latent for years;
with file reaping it became data loss — files beyond page one were reaped and
the download finalised incomplete. - A quarantined download's "delete local files" could
rm -rfthe profile's
download root, or a directory above it, via a transfer named.., which
comes from torrent metadata. Deleting one download could also delete a nested
download's files. Files are now deleted only when exactly one download owns
them, never from a folder holding another download, and never resolved
against the working directory. - A spoofable
User-Agentheader could delete another profile's put.io
transfer. RPC auth is off by default, so the header was the only barrier. - Ownership was re-derived at boot.
getDefaultProfile()— "slug
default, else whatever row is first" — was the fallback at nine sites that
decide where files get written, and a boot-time sweep rewrote ownership on
every start, so an *arr download could silently stage into a grab profile's
folder. - *Adding a second arr profile broke the first one: the shared endpoint
stopped resolving by path, including for the profile that owned
/transmission/rpc. - **`torrent...
v2.0.3
v2.0.2
What's Changed
- Fix section hierarchy and action overflow by @ptheofan in #47
- Fix download item matching and layout by @ptheofan in #49
- Make RPC profiles and put.io IDs authoritative transfer links by @ptheofan in #51
- Show Put.io transfer status details by @ptheofan in #53
- Release v2.0.2 by @ptheofan in #54
Full Changelog: v2.0.1...v2.0.2
v2.0.1
What's Changed
- Redesign downloads screen and modularize app.css into styles/ partials by @jchiotaka in #33
- [codex] Fix cancelled Put.io transfer pruning by @ptheofan in #40
- [codex] Add multi-select download delete by @ptheofan in #41
- Fix completed Prowlarr downloads staying visible by @ptheofan in #42
- CI: publish :main container image on push to main by @ptheofan in #43
- CI: run on pull requests only (stop duplicating gates on main pushes) by @ptheofan in #44
Full Changelog: v2.0.0...v2.0.1