Skip to content

feat: download client cleanup for Radarr/Sonarr deletions (qBittorrent)#3054

Merged
enoch85 merged 1 commit into
developmentfrom
feat/qbit-support
Jun 6, 2026
Merged

feat: download client cleanup for Radarr/Sonarr deletions (qBittorrent)#3054
enoch85 merged 1 commit into
developmentfrom
feat/qbit-support

Conversation

@enoch85

@enoch85 enoch85 commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Adds an optional download-client integration that removes the completed download (and optionally its data) from qBittorrent when Radarr/Sonarr deletes media — cleaning up the orphaned, still-seeding download left behind after the library copy is removed.

  • Matched via the Radarr/Sonarr download history (downloadId), so it only runs for media removed through Radarr/Sonarr. Sonarr is scoped to whole-show deletes (season-pack safety).
  • Whether a download has finished seeding is decided by qBittorrent's own ratio / seed-time limits; a fallback ratio (default 0.5, min 0.5) applies only to downloads qBittorrent doesn't limit.
  • qBittorrent deletes its own downloaded files, so the common "downloads separate from the library" (hardlink/copy) setup is cleaned without Maintainerr needing any paths. Cross-seed-safe: a shared content path is removed entry-only.
  • Settings tab shown only when Radarr/Sonarr is configured; handles qBittorrent's auth-bypass and surfaces an actionable message on a 403.
  • Backend sits behind a DownloadClient interface + factory so additional clients can be added later.

Includes a TypeORM migration for the new settings columns plus unit and UI tests. Docs (a Configuration section) are prepared as a separate Maintainerr_docs change.

Comment thread apps/server/src/modules/api/download-client-api/helpers/qbittorrent.helper.ts Dismissed
Comment thread apps/server/src/modules/api/download-client-api/helpers/qbittorrent.helper.ts Dismissed
Comment thread apps/server/src/modules/api/download-client-api/helpers/qbittorrent.helper.ts Dismissed
Comment thread apps/server/src/modules/api/download-client-api/helpers/qbittorrent.helper.ts Dismissed
Comment thread apps/server/src/modules/api/download-client-api/helpers/qbittorrent.helper.ts Dismissed
@enoch85

enoch85 commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

Reason for dismissing alerts

Requests intentionally target an administrator-configured download-client URL, exactly like Maintainerr's existing Plex/Jellyfin/Emby/Radarr/Sonarr/Seerr/Tautulli/Streamystats integrations. Maintainerr is self-hosted and by design connects to user-configured services on private networks (localhost/RFC1918); blocking those is not viable (see serviceUrlSchema), and settings endpoints are protected at the network level. Not an exploitable SSRF in this threat model.

When Radarr/Sonarr delete media, Maintainerr can remove the matching completed
download (and optionally its data) from the configured download client,
cleaning up the orphaned, still-seeding torrent left behind after the library
copy is removed.

- Matched via the Radarr/Sonarr download history (downloadId), so it only runs
  for media removed through Radarr/Sonarr. Season/episode deletes remove only
  the torrents the delete fully covers (every backed episode is being deleted),
  so a season-pack / complete-series pack that still backs wanted episodes is
  left intact. Removal runs only after the *arr delete is confirmed.
- Whether a download has finished seeding is decided by qBittorrent's own ratio
  / seed-time limits; a fallback ratio (default 0.5, min 0.5) applies only to
  downloads qBittorrent doesn't limit.
- qBittorrent deletes its own downloaded files, so the common downloads-separate-
  from-library (hardlink/copy) setup is cleaned without Maintainerr needing any
  paths. Cross-seed-safe: a shared content path is removed entry-only.
- Settings tab shown only when Radarr/Sonarr is configured; handles qBittorrent's
  auth-bypass and surfaces an actionable message on a 403.
- Backend sits behind a DownloadClient interface + factory so additional clients
  can be added later.

Includes a TypeORM migration for the new settings columns plus unit and UI tests.
@enoch85 enoch85 force-pushed the feat/qbit-support branch from cc7d4f6 to 15723f9 Compare June 6, 2026 21:11
@enoch85 enoch85 merged commit 8a8b26e into development Jun 6, 2026
15 of 16 checks passed
@enoch85 enoch85 deleted the feat/qbit-support branch June 6, 2026 22:28
maintainerr-automation Bot added a commit that referenced this pull request Jun 8, 2026
* feat: download client cleanup for Radarr/Sonarr deletions (qBittorrent) (#3054)

* fix(metadata): keep media-server ids unless corroborated; accept on provider year agreement (#3011)

* fix(overlay): uniform style scaling, rotation offset, and font register logging (fixes #3025) (#3057)

* build(deps): bump @types/react in the react group (#3060)

Bumps the react group with 1 update: [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react).


Updates `@types/react` from 19.2.16 to 19.2.17
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

---
updated-dependencies:
- dependency-name: "@types/react"
  dependency-version: 19.2.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: react
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump the nestjs group with 5 updates (#3059)

Bumps the nestjs group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common) | `11.1.24` | `11.1.26` |
| [@nestjs/core](https://github.com/nestjs/nest/tree/HEAD/packages/core) | `11.1.24` | `11.1.26` |
| [@nestjs/platform-express](https://github.com/nestjs/nest/tree/HEAD/packages/platform-express) | `11.1.24` | `11.1.26` |
| [@nestjs/cli](https://github.com/nestjs/nest-cli) | `11.0.21` | `11.0.22` |
| [@nestjs/testing](https://github.com/nestjs/nest/tree/HEAD/packages/testing) | `11.1.24` | `11.1.26` |


Updates `@nestjs/common` from 11.1.24 to 11.1.26
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.26/packages/common)

Updates `@nestjs/core` from 11.1.24 to 11.1.26
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.26/packages/core)

Updates `@nestjs/platform-express` from 11.1.24 to 11.1.26
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.26/packages/platform-express)

Updates `@nestjs/cli` from 11.0.21 to 11.0.22
- [Release notes](https://github.com/nestjs/nest-cli/releases)
- [Commits](nestjs/nest-cli@11.0.21...11.0.22)

Updates `@nestjs/testing` from 11.1.24 to 11.1.26
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.26/packages/testing)

---
updated-dependencies:
- dependency-name: "@nestjs/common"
  dependency-version: 11.1.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/core"
  dependency-version: 11.1.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/platform-express"
  dependency-version: 11.1.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/cli"
  dependency-version: 11.0.22
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/testing"
  dependency-version: 11.1.26
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: nestjs
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump react-hook-form from 7.77.0 to 7.78.0 (#3061)

Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.77.0 to 7.78.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](react-hook-form/react-hook-form@v7.77.0...v7.78.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.78.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @types/node from 22.19.19 to 22.19.20 (#3062)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.19.19 to 22.19.20.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.19.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(ui): move the beta badge from notifications to the download client (#3058)

Notifications has graduated from beta; the download client is the
current beta feature. Reuses the same beta.svg heading badge.

* fix(plex): use /identity for the connection probe and v2 resources for server discovery (#3063)

* fix(plex): probe /identity instead of / for the connection check

Bare / returns 401 behind a reverse proxy (it redirects to the web UI),
so getStatus()/setMachineId() failed and the Plex adapter wouldn't
initialize for proxied servers. /identity returns the same
machineIdentifier and version without the auth quirk and works for
direct and proxied servers alike.

* fix(plex): discover servers via plex.tv v2 resources

The legacy v1 /api/resources omits some owned servers (and needs no
client id), so the server-selection list could come back empty even
when plex.tv knows about the server. Switch getDevices() to the v2 JSON
/api/v2/resources, which returns those servers; it requires
X-Plex-Client-Identifier, so send the instance clientId (the same id the
UI authenticates with) plus X-Plex-Product.

* fix(ui): drop the flickering "Validating Plex token" banner

Stored-token validation is fast, so the info banner only flashes. Remove
it; a failed validation is still surfaced by the stored-token result
alert, and the "configuration required" prompt stays suppressed while a
stored token is being validated (so it doesn't flash in its place).

* fix(plex): keep dnsRebindingProtection/natLoopbackSupported in v2 device mapping

The v2 /api/v2/resources rewrite stopped populating these two fields that
the legacy v1 mapping set. v2 still returns them, so restore them for
parity (they are optional and currently unread, but shouldn't silently
become undefined).

* refactor(plex): remove unused getWatchlist and getDiscoverDataUserState helpers (#3064)

* refactor(plex): remove unused PlexTvApi.getWatchlist

getWatchlist() and its WatchlistResponse/MetadataResponse/PlexWatchlistItem
types have no production callers. The live Plex watchlist-rule path is
plex-getter -> getWatchlistIdsForUser -> PlexCommunityApi (community.plex.tv
GraphQL); this helper was superseded leftover. Pure removal, no behavior change.

* refactor(plex): remove unused getDiscoverDataUserState

getDiscoverDataUserState() and its PlexDiscoverUserState /
PlexDiscoverUserStateResponse types were added in 2023 'for potential
future usage' and never called. Drop the dormant helper to keep the API
surface clean.

* test(ui): stabilize external service settings spec

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: maintainerr-automation[bot] <261505141+maintainerr-automation[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>
Co-authored-by: Craig Linford <clinford86@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.15.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

doonga pushed a commit to greyrock-labs/home-ops that referenced this pull request Jun 11, 2026
… ➔ 3.15.0) (#230)

This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/maintainerr/maintainerr](https://github.com/Maintainerr/Maintainerr) | minor | `3.14.0` → `3.15.0` |

---

### Release Notes

<details>
<summary>Maintainerr/Maintainerr (ghcr.io/maintainerr/maintainerr)</summary>

### [`v3.15.0`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3150-2026-06-09)

[Compare Source](Maintainerr/Maintainerr@v3.14.0...v3.15.0)

#### Highlights

- Added functionality to delete items from the download client and manage collection membership within the media server.
- Optional integration for qBittorrent to remove completed downloads when Radarr/Sonarr deletes media ([#&#8203;3054](Maintainerr/Maintainerr#3054)).
- Plex connection fixes: updated server discovery to use v2 API and improved connection probe reliability ([#&#8203;3063](Maintainerr/Maintainerr#3063)).

#### Features

- Added item deletion and collection management features in the media server.
- Optional download-client integration for qBittorrent to clean up completed downloads when Radarr/Sonarr removes media ([#&#8203;3054](Maintainerr/Maintainerr#3054)).

#### Fixes

- Fixed Plex connection probe to use `/identity` endpoint and updated server discovery to use v2 API resources ([#&#8203;3063](Maintainerr/Maintainerr#3063)).
- Resolved overlay rendering issues with uniform style scaling, rotation anchoring, and improved font register logging ([#&#8203;3057](Maintainerr/Maintainerr#3057)).
- Improved metadata handling to retain media-server IDs unless corroborated by provider data, with additional checks for year agreement ([#&#8203;3011](Maintainerr/Maintainerr#3011)).

#### Database migrations

- Added new columns to the `settings` table for download client configuration, including URL, credentials, and deletion settings.

#### Internal

- Removed unused Plex API helpers: `getWatchlist` and `getDiscoverDataUserState` ([#&#8203;3064](Maintainerr/Maintainerr#3064)).

#### Dependencies

- Updated 8 dependencies, including `typescript-eslint`, `@types/node`, and `react-hook-form`.

</details>

---

### Configuration

📅 **Schedule**: (in timezone America/New_York)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMTQuNiIsInVwZGF0ZWRJblZlciI6IjQzLjIxNC42IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19-->

Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/230
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants