{"id":13522364,"url":"https://github.com/rpatterson/prunerr","last_synced_at":"2025-04-28T02:32:03.784Z","repository":{"id":64686937,"uuid":"577117790","full_name":"rpatterson/prunerr","owner":"rpatterson","description":"Perma-seed Servarr media libraries","archived":false,"fork":false,"pushed_at":"2024-12-01T01:52:03.000Z","size":6811,"stargazers_count":35,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2025-04-05T05:33:11.337Z","etag":null,"topics":["bittorent","radarr","servarr","sonarr","torrent","transmission"],"latest_commit_sha":null,"homepage":"https://gitlab.com/rpatterson/prunerr","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rpatterson.png","metadata":{"files":{"readme":"README.rst","changelog":"NEWS-VERSION.rst","contributing":"docs/contributing.rst","funding":null,"license":"LICENSES/MIT.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-12T02:02:43.000Z","updated_at":"2025-03-18T10:54:20.000Z","dependencies_parsed_at":"2022-12-14T15:39:43.534Z","dependency_job_id":"204d0415-6dd4-4bba-9f81-ba20c113b0c1","html_url":"https://github.com/rpatterson/prunerr","commit_stats":null,"previous_names":[],"tags_count":60,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpatterson%2Fprunerr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpatterson%2Fprunerr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpatterson%2Fprunerr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rpatterson%2Fprunerr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rpatterson","download_url":"https://codeload.github.com/rpatterson/prunerr/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251238208,"owners_count":21557418,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bittorent","radarr","servarr","sonarr","torrent","transmission"],"created_at":"2024-08-01T06:00:46.117Z","updated_at":"2025-04-28T02:32:03.285Z","avatar_url":"https://github.com/rpatterson.png","language":"Python","funding_links":["https://liberapay.com/rpatterson/donate"],"categories":["Complimenting Apps"],"sub_categories":[],"readme":".. SPDX-FileCopyrightText: 2023 Ross Patterson \u003cme@rpatterson.net\u003e\n..\n.. SPDX-License-Identifier: MIT\n\n########################################################################################\nPrunerr\n########################################################################################\nPerma-seed Servarr media libraries\n****************************************************************************************\n\n.. list-table::\n   :class: borderless align-right\n\n   * - .. figure:: https://img.shields.io/pypi/v/prunerr.svg?logo=pypi\u0026label=PyPI\u0026logoColor=gold\n          :alt: PyPI latest release version\n          :target: https://pypi.org/project/prunerr/\n       .. figure:: https://img.shields.io/pypi/pyversions/prunerr.svg?logo=python\u0026label=Python\u0026logoColor=gold\n          :alt: PyPI Python versions\n          :target: https://pypi.org/project/prunerr/\n       .. figure:: https://img.shields.io/badge/code%20style-black-000000.svg\n          :alt: Python code style\n          :target: https://github.com/psf/black\n       .. figure:: https://api.reuse.software/badge/gitlab.com/rpatterson/prunerr\n          :alt: Reuse license status\n          :target: https://api.reuse.software/info/gitlab.com/rpatterson/prunerr\n\n     - .. figure:: https://gitlab.com/rpatterson/prunerr/-/badges/release.svg\n          :alt: GitLab latest release\n          :target: https://gitlab.com/rpatterson/prunerr/-/releases\n       .. figure:: https://gitlab.com/rpatterson/prunerr/badges/main/pipeline.svg\n          :alt: GitLab CI/CD pipeline status\n          :target: https://gitlab.com/rpatterson/prunerr/-/commits/main\n       .. figure:: https://gitlab.com/rpatterson/prunerr/badges/main/coverage.svg\n          :alt: GitLab coverage report\n          :target: https://gitlab.com/rpatterson/prunerr/-/commits/main\n       .. figure:: https://img.shields.io/gitlab/stars/rpatterson/prunerr?gitlab_url=https%3A%2F%2Fgitlab.com\u0026logo=gitlab\n          :alt: GitLab repository stars\n          :target: https://gitlab.com/rpatterson/prunerr\n\n     - .. figure:: https://img.shields.io/github/v/release/rpatterson/prunerr?logo=github\n          :alt: GitHub release (latest SemVer)\n          :target: https://github.com/rpatterson/prunerr/releases\n       .. figure:: https://github.com/rpatterson/prunerr/actions/workflows/build-test.yml/badge.svg\n          :alt: GitHub Actions status\n          :target: https://github.com/rpatterson/prunerr/actions/workflows/build-test.yml\n       .. figure:: https://codecov.io/github/rpatterson/prunerr/branch/main/graph/badge.svg?token=GNKVQ8VYOU\n          :alt: Codecov test coverage\n          :target: https://app.codecov.io/github/rpatterson/prunerr\n       .. figure:: https://img.shields.io/github/stars/rpatterson/prunerr?logo=github\n          :alt: GitHub repository stars\n          :target: https://github.com/rpatterson/prunerr/\n\n     - .. figure:: https://img.shields.io/docker/v/merpatterson/prunerr?sort=semver\u0026logo=docker\n          :alt: Docker Hub image version\n          :target: https://hub.docker.com/r/merpatterson/prunerr\n       .. figure:: https://img.shields.io/docker/pulls/merpatterson/prunerr?logo=docker\n          :alt: Docker Hub image pulls count\n          :target: https://hub.docker.com/r/merpatterson/prunerr\n       .. figure:: https://img.shields.io/docker/stars/merpatterson/prunerr?logo=docker\n          :alt: Docker Hub stars\n          :target: https://hub.docker.com/r/merpatterson/prunerr\n       .. figure:: https://img.shields.io/docker/image-size/merpatterson/prunerr?logo=docker\n          :alt: Docker Hub image size\n          :target: https://hub.docker.com/r/merpatterson/prunerr\n\n     - .. figure:: https://img.shields.io/keybase/pgp/rpatterson?logo=keybase\n          :alt: KeyBase Pretty Good Privacy (PGP) key ID\n          :target: https://keybase.io/rpatterson\n       .. figure:: https://img.shields.io/github/followers/rpatterson?style=social\n          :alt: GitHub followers count\n          :target: https://github.com/rpatterson\n       .. figure:: https://img.shields.io/liberapay/receives/rpatterson.svg?logo=liberapay\n          :alt: LiberaPay donated per week\n          :target: https://liberapay.com/rpatterson/donate\n       .. figure:: https://img.shields.io/liberapay/patrons/rpatterson.svg?logo=liberapay\n          :alt: LiberaPay patrons count\n          :target: https://liberapay.com/rpatterson/donate\n\n\nTL;DR: Perma-seeding of whole Servarr libraries optimized for per-tracker ratio.\n\n- Delete torrents/items `only as disk space gets low\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L32-45\u003e`_.\n- Don't delete `currently imported items\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L190-199\u003e`_.\n  IOW, only delete upgraded items.\n- Don't delete `private items that haven't met seeding requirements\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L162-189\u003e`_.\n- Delete `public items first\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L154-156\u003e`_.\n- Delete private items in `an order to maximize tracker ratio and/or bonuses\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L200-221\u003e`_.\n- Delete `stalled items\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L63-89\u003e`_\n  , and `items containing archives such as *.rar\n  releases\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L90-109\u003e`_\n  and `blacklist them\n  \u003chttps://gitlab.com/rpatterson/prunerr/-/blob/main/src/prunerr/home/.config/prunerr.yml#L109\u003e`_,\n  AKA mark them as failed, in Servarr.\n- And more...\n\n.. include-end-before\n.. contents:: Table of Contents\n.. include-start-after\n\n\n****************************************************************************************\nSummary\n****************************************************************************************\n\nSeed Servarr download client torrents/items as long as possible only deleting them as\nnecessary as disk space gets low, hence the name based on \"to prune\". Which download\nitems are considered eligible for deletion is configured by the user. The common case is\nthat download items that are currently imported are not considered for deletion.\nNeither are items from private trackers/indexers that have been upgraded or otherwise\ndeleted from the library but haven't met the indexers seeding requirements. The order in\nwhich download items are deleted is determined according to rules configured by the\nuser. The common case is to delete items from public indexers first and among those to\ndelete the items with the highest ratio first to preserve the health of the community by\nseeding less popular items longer. Next delete items from private indexers by configured\nindexer priority and within the items for a given indexer to delete items in an order to\nmaximize ratio and/or seeding rewards.\n\nOther configured operations may be applied as well. For example:\n\n- Verify and resume corrupt items\n- Increase bandwidth priority for items from private indexers\n- Decrease bandwidth priority for items from public indexers\n- Remove and blacklist download items containing archives (``*.rar``, ``*.zip``,\n  ``*.tar.gz``, etc.) which can't be perma-seeded\n- Remove and blacklist stalled download items\n- etc.\n\nThe ``$ prunerr`` command is intended to serve as a companion to the `Servarr`_ suite of\napplications and services and the `Transmission BitTorrent client`_. It periodically\npolls the `download clients`_ of `Sonarr`_, `Radarr`_, etc. and applies the configured\noperations to the download items in each of those download clients. It can also be run\nindependently of any Servarr instances to optimize seeding for download items added by\nother means, e.g. `FlexGet`_.\n\nSee the `Usage`_ section below for full details.\n\n\n****************************************************************************************\nInstallation\n****************************************************************************************\n\nInstall locally or use the Docker container image:\n\nLocal Installation\n========================================================================================\n\nInstall by using any tool for installing standard Python 3 distributions such as\n`pip`_::\n\n  $ pip3 install --user prunerr\n\nOptional shell prompt tab completion is available by using `argcomplete`_.\n\nDocker Container Image\n========================================================================================\n\nThe recommended way to use the container image is by using `Docker Compose`_. See `the\nexample ./docker-compose.yml file`_. Write your configuration and run the container::\n\n  $ docker compose up\n\nYou can also use the image directly. Pull `the Docker image`_. Use it to create and run\na container::\n\n  $ docker pull \"registry.gitlab.com/rpatterson/prunerr\"\n  $ docker run --rm -it \"registry.gitlab.com/rpatterson/prunerr\" ...\n\nUse image variant tags to control when the image updates. Releases publish tags for the\nbranch and for major and minor versions. For example, to keep up to date with a specific\nbranch, use a tag such as\n``registry.gitlab.com/rpatterson/prunerr:main``. Releases from ``develop``\npublish pre-releases. Releases from ``main`` publish final releases. Releases from\n``main`` also publish tags without a branch, for example\n``registry.gitlab.com/rpatterson/prunerr``. Releases from ``main`` also\npublish tags for the major and minor version, for example\n``registry.gitlab.com/rpatterson/prunerr:v0.8``.\n\nReleases publish multi-platform images for the following platforms:\n\n- ``linux/amd64``\n- ``linux/arm64``\n- ``linux/arm/v7``\n\n\n****************************************************************************************\nUsage\n****************************************************************************************\n\nStart by writing your ``~/.config/prunerr.yml`` configuration file. See the comments in\n`the example configuration`_ for details.\n\nOnce configured, you may run individual sub-commands once, run all operations once as\nconfigured using the ``$ prunerr exec`` sub-command, or run all operations in a polling\nloop using the ``$ prunerr daemon`` sub-command. See the `Order of Operations`_ section\nfor a detailed description of the operations. Use the CLI help to list the other\nsub-commands and to get help on the individual sub-commands::\n\n  $ prunerr --help\n  $ prunerr exec --help\n\nIf using the Docker container image, the container can be run from the command-line as\nwell::\n\n  $ docker compose run \"prunerr\" prunerr --help\n\n\n****************************************************************************************\nOrder of Operations\n****************************************************************************************\n\nNote that polling is required because there is no event we can subscribe to that\nreliably determines disk space margin *as* the download clients are downloading. Every\nrun of the ``$ prunerr exec`` sub-command or every loop of the ``$ prunerr daemon``\nsub-command performs the following operations.\n\n#. Verify and resume corrupt items, same as: ``$ prunerr verify``.\n\n#. Review download items, same as: ``$ prunerr review``:\n\n   Apply per-indexer review operations as configured under ``indexers/reviews`` in the\n   configuration file to all download items.\n\n#. Move download items that have been acted on by Servarr to the ``*/seeding/*``\n   directory, same as: ``$ prunerr move``.\n\n   As Servarr acts on completed download items, be that importing files from them,\n   ignoring them, deleting them from the queue, etc., Prunerr moves those items from the\n   Servarr download client's ``Directory`` to a parallel ``*/seeding/*`` directory.\n   Then when deleting download items to free space, Prunerr only considers items under\n   that directory. This has the added benefit of reflecting which items have been acted\n   on by Servarr in the download client.\n\n#. Delete download items if disk space is low, same as: ``$ prunerr free-space``.\n\n   Consider items for deletion in different groups in this order:\n\n   #. Download items no longer registered with tracker.\n\n      IOW, items that can no longer be seeded at all first.\n\n   #. Orphan files and directories not belonging to any download item\n\n      Walk all the top-level directories used by each download client and identify which\n      paths don't correspond to a download client item.\n\n      .. warning::\n         Stop Prunerr before moving any download items.\n\n         Prunerr uses the list of files for every download item to identify orphans. If\n         a user, or anything else other than Prunerr, moves or changes the location of a\n         download item while Prunerr is identifying orphans, Prunerr may identify the\n         *new* download item data path as an orphan and delete it out from under the\n         download item leading to data loss.\n\n   #. Imported/seeding download items\n\n      IOW, download items that have been acted upon by Servarr and moved to the\n      ``*/seeding/*`` directory by the ``$ prunerr move`` sub-command/operation\n      excluding those items filtered out according to the ``indexers/priorities``\n      operations with ``filter: true``. For example, don't delete currently imported\n      items (by hard link count) or items that haven't met private indexer seeding\n      requirements.\n\n   For each of these groups in order, loop through each item in the group and:\n\n   #. Check disk space against the margin configured by\n      ``download-clients/*/max-download-bandwidth`` and\n      ``download-clients/*/min-download-time-margin``\n\n   #. If there's insufficient disk space, delete the item.\n\n   For the orphans group, delete smaller items first to minimize the amount of\n   re-downloading needed should the user notice and correct any issues resulting in the\n   orphans.\n\n   For the other groups delete items in the order determined by the configured\n   ``indexers/priorities`` indexer order then by the configured operations for that\n   item's indexer.\n\n   For those times when there's nothing Prunerr can delete to free disk space, most\n   users' download clients should also probably pause downloading when disk space drops\n   significantly below this margin. Use `the provided Transmission pause download\n   script`_ and, optionally, `integrate it into your Docker Compose project via a cron\n   job`_ or see those as examples.\n\n\n****************************************************************************************\nExport sub-command\n****************************************************************************************\n\nThe ``export`` sub-command is roughly the inverse of Servarr import events, hard link\nimported files back into download client items and verify. See the CLI ``$ prunerr\nexport --help`` output for more details.\n\n****************************************************************************************\nContributing\n****************************************************************************************\n\n`GitLab hosts this project`_ and `mirrors it to GitHub`_ but use GitLab for reporting\nissues, submitting pull or merge requests and any other development or maintenance\nactivity. See `the contributing documentation`_ for more details on how to get started\nwith development.\n\n\n****************************************************************************************\nMotivation\n****************************************************************************************\n\nI didn't like the available options I could find at the time for maximizing seeding from\na lovingly managed media library. Deleting by a ratio threshold doesn't make sense to me\nbecause that can delete items when there's plenty of disk space. Also the ratio\nthreshold is a reverse indicator for items from private indexers vs items from public\nindexers. Items from private indexers with high ratios should be kept around as long as\npossible to build user total ratio whereas items from public indexers with low ratios\nshould be kept around as long as possible to preserve access in the community/ecosystem.\nFinally, deleting any item still imported in the Servarr only because it hit the ratio\nthreshold is the biggest waste since it doesn't free any space. So I wrote Prunerr to\nprune download items in the correct order.\n\nThe use case for Prunerr is not tracker ratio racing. It's goal is to seed as long as\npossible and to seed as much of your library as possible. This should have some\nsecondary benefits to ratio, but that's not the main goal.\n\nFinally, there is a laundry list of other download client management tasks that can be\nautomated but aren't by anything I could find. So I added them to Prunerr as well.\n\n\n****************************************************************************************\nReferences\n****************************************************************************************\n\n.. target-notes::\n\n.. _`Servarr`: https://wiki.servarr.com\n.. _`Transmission BitTorrent client`: https://transmissionbt.com/\n.. _`download clients`: https://wiki.servarr.com/radarr/settings#download-clients\n.. _`Sonarr`: https://wiki.servarr.com/en/sonarr\n.. _`Radarr`: https://wiki.servarr.com/en/radarr\n.. _`FlexGet`: https://flexget.com/\n\n.. _pip: https://pip.pypa.io/en/stable/installation/\n.. _argcomplete: https://kislyuk.github.io/argcomplete/#installation\n\n.. _`Docker Compose`: https://docs.docker.com/compose/\n.. _`the example ./docker-compose.yml file`:\n   https://gitlab.com/rpatterson/prunerr/-/blob/main/docker-compose.yml\n.. _the Docker image: https://hub.docker.com/r/merpatterson/prunerr\n\n.. _`the example configuration`:\n   https://gitlab.com/rpatterson/prunerr/blob/main/src/prunerr/home/.config/prunerr.yml\n.. _`the provided Transmission pause download script`:\n   https://gitlab.com/rpatterson/prunerr/blob/main/transmission/usr/local/bin/transmission-pause-download\n.. _`integrate it into your Docker Compose project via a cron job`:\n   https://gitlab.com/rpatterson/prunerr/blob/main/transmission/etc/crontabs/abc\n\n.. _`GitLab hosts this project`:\n   https://gitlab.com/rpatterson/prunerr\n.. _`mirrors it to GitHub`:\n   https://github.com/rpatterson/prunerr\n.. _`the contributing documentation`:\n   https://gitlab.com/rpatterson/prunerr/-/blob/main/docs/contributing.rst\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frpatterson%2Fprunerr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frpatterson%2Fprunerr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frpatterson%2Fprunerr/lists"}