Add documentation (#125)

This commit is contained in:
Flaminel
2025-05-04 15:21:41 +03:00
committed by GitHub
parent 75b001cf6a
commit b92d70769a
74 changed files with 28406 additions and 1160 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
github: Flaminel

49
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Deploy Docusaurus to GitHub Pages
on:
push:
branches: [main]
paths:
- 'docs/**'
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: yarn
cache-dependency-path: docs/yarn.lock
- name: Install dependencies
working-directory: docs
run: yarn install --frozen-lockfile
- name: Build Docusaurus
working-directory: docs
run: yarn build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/build
retention-days: 1
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

396
README.md
View File

@@ -1,409 +1,29 @@
_Love this project? Give it a ⭐️ and let others know!_
# <img width="24px" src="./Logo/256.png" alt="cleanuperr"></img> cleanuperr
# <img width="24px" src="./Logo/256.png" alt="cleanuperr"></img> Cleanuperr
[![Discord](https://img.shields.io/discord/1306721212587573389?color=7289DA&label=Discord&style=for-the-badge&logo=discord)](https://discord.gg/sWggpnmGNY)
cleanuperr is a tool for automating the cleanup of unwanted or blocked files in Sonarr, Radarr, and supported download clients like qBittorrent. It removes incomplete or blocked downloads, updates queues, and enforces blacklists or whitelists to manage file selection. After removing blocked content, cleanuperr can also trigger a search to replace the deleted shows/movies.
Cleanuperr is a tool for automating the cleanup of unwanted or blocked files in Sonarr, Radarr, and supported download clients like qBittorrent. It removes incomplete or blocked downloads, updates queues, and enforces blacklists or whitelists to manage file selection. After removing blocked content, Cleanuperr can also trigger a search to replace the deleted shows/movies.
cleanuperr was created primarily to address malicious files, such as `*.lnk` or `*.zipx`, that were getting stuck in Sonarr/Radarr and required manual intervention. Some of the reddit posts that made cleanuperr come to life can be found [here](https://www.reddit.com/r/sonarr/comments/1gqnx16/psa_sonarr_downloaded_a_virus/), [here](https://www.reddit.com/r/sonarr/comments/1gqwklr/sonar_downloaded_a_mkv_file_which_looked_like_a/), [here](https://www.reddit.com/r/sonarr/comments/1gpw2wa/downloaded_waiting_to_import/) and [here](https://www.reddit.com/r/sonarr/comments/1gpi344/downloads_not_importing_no_files_found/).
Cleanuperr was created primarily to address malicious files, such as `*.lnk` or `*.zipx`, that were getting stuck in Sonarr/Radarr and required manual intervention. Some of the reddit posts that made Cleanuperr come to life can be found [here](https://www.reddit.com/r/sonarr/comments/1gqnx16/psa_sonarr_downloaded_a_virus/), [here](https://www.reddit.com/r/sonarr/comments/1gqwklr/sonar_downloaded_a_mkv_file_which_looked_like_a/), [here](https://www.reddit.com/r/sonarr/comments/1gpw2wa/downloaded_waiting_to_import/) and [here](https://www.reddit.com/r/sonarr/comments/1gpi344/downloads_not_importing_no_files_found/).
> [!IMPORTANT]
> **Features:**
> - Strike system to mark stalled or downloads stuck in metadata downloading.
> - Remove and block downloads that reached a maximum number of strikes.
> - Remove and block downloads that have a low download speed or high estimated completion time.
> - Remove downloads blocked by qBittorrent or by cleanuperr's **content blocker**.
> - Remove downloads blocked by qBittorrent or by Cleanuperr's **content blocker**.
> - Trigger a search for downloads removed from the *arrs.
> - Clean up downloads that have been seeding for a certain amount of time.
> - Notify on strike or download removal.
> - Ignore certain torrent hashes, categories, tags or trackers from processing.
> - Ignore certain torrent hashes, categories, tags or trackers from being processed by Cleanuperr.
cleanuperr supports both qBittorrent's built-in exclusion features and its own blocklist-based system. Binaries for all platforms are provided, along with Docker images for easy deployment.
Cleanuperr supports both qBittorrent's built-in exclusion features and its own blocklist-based system. Binaries for all platforms are provided, along with Docker images for easy deployment.
> [!WARNING]
> This tool is actively developed and still a work in progress, so using the `latest` Docker tag may result in breaking changes. Join the Discord server if you want to reach out to me quickly (or just stay updated on new releases) so we can squash those pesky bugs together:
>
> https://discord.gg/sWggpnmGNY
# Docs
## Table of contents:
- [Naming choice](#naming-choice)
- [Quick Start](#quick-start)
- [How it works](#how-it-works)
- [Content blocker](#1-content-blocker-will)
- [Queue cleaner](#2-queue-cleaner-will)
- [Download cleaner](#3-download-cleaner-will)
- [Setup](#setup-examples)
- [Usage](#usage)
- [Docker](#docker)
- [Environment Variables](#environment-variables)
- [Docker Compose](#docker-compose-example)
- [Windows](#windows)
- [Linux](#linux)
- [MacOS](#macos)
- [FreeBSD](#freebsd)
- [Credits](#credits)
## Naming choice
I've had people asking why it's `cleanuperr` and not `cleanuparr` and that I should change it. This name was intentional.
I've seen a few discussions on this type of naming and I've decided that I didn't deserve the `arr` moniker since `cleanuperr` is not a fork of `NZB.Drone` and it does not have any affiliation with the arrs. I still wanted to keep the naming style close enough though, to suggest a correlation between them.
## Quick Start
> [!NOTE]
>
> 1. **Docker (Recommended)**
> Pull the Docker image from `ghcr.io/flmorg/cleanuperr:latest`.
>
> 2. **Unraid (for Unraid users)**
> Use the Unraid Community App.
>
> 3. **Manual Installation (if you're not using Docker)**
> Go to [Windows](#windows), [Linux](#linux) or [MacOS](#macos).
> [!TIP]
> Refer to the [Environment variables](#environment-variables) section for detailed configuration instructions and the [Setup examples](#setup-examples) section for an in-depth explanation of the cleanup process.
> [!IMPORTANT]
> Only the **latest versions** of the following apps are supported, or earlier versions that have the same API as the latest version:
> - qBittorrent
> - Deluge
> - Transmission
> - Sonarr
> - Radarr
> - Lidarr
# How it works
#### 1. **Content blocker** will:
- Run every 5 minutes (or configured cron).
- Process all items in the *arr queue.
- Find the corresponding item from the download client for each queue item.
- Mark the files that were found in the queue as **unwanted/skipped** if:
- They **are listed in the blacklist**, or
- They **are not included in the whitelist**.
- If **all files** of a download **are unwanted**:
- It will be removed from the *arr's queue and blocked.
- It will be deleted from the download client.
- A new search will be triggered for the *arr item.
#### 2. **Queue cleaner** will:
- Run every 5 minutes (or configured cron, or right after `content blocker`).
- Process all items in the *arr queue.
- Check each queue item if it is **stalled (download speed is 0)**, **stuck in metadata downloading**, **failed to be imported** or **slow**.
- If it is, the item receives a **strike** and will continue to accumulate strikes every time it meets any of these conditions.
- Check each queue item if it meets one of the following condition in the download client:
- **Marked as completed, but 0 bytes have been downloaded** (due to files being blocked by qBittorrent or the **content blocker**).
- All associated files are marked as **unwanted/skipped/do not download**.
- If the item **DOES NOT** match the above criteria, it will be skipped.
- If the item **DOES** match the criteria or has received the **maximum number of strikes**:
- It will be removed from the *arr's queue and blocked.
- It will be deleted from the download client.
- A new search will be triggered for the *arr item.
#### 3. **Download cleaner** will:
- Run every hour (or configured cron).
- Automatically clean up downloads that have been seeding for a certain amount of time.
# Setup examples
## Using qBittorrent's built-in feature (works only with qBittorrent)
1. Go to qBittorrent -> Options -> Downloads -> make sure `Excluded file names` is checked -> Paste an exclusion list that you have copied.
- [blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), or
- [permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive), or
- create your own
2. qBittorrent will block files from being downloaded. In the case of malicious content, **nothing is downloaded and the torrent is marked as complete**.
3. Start **cleanuperr** with `QUEUECLEANER__ENABLED` set to `true`.
4. The **queue cleaner** will perform a cleanup process as described in the [How it works](#how-it-works) section.
## Using cleanuperr's blocklist (works with all supported download clients)
1. Set both `QUEUECLEANER__ENABLED` and `CONTENTBLOCKER__ENABLED` to `true` in your environment variables.
2. Configure and enable either a **blacklist** or a **whitelist** as described in the [Arr variables](variables.md#Arr-settings) section.
3. Once configured, cleanuperr will perform the following tasks:
- Execute the **content blocker** job, as explained in the [How it works](#how-it-works) section.
- Execute the **queue cleaner** job, as explained in the [How it works](#how-it-works) section.
## Using cleanuperr just for failed *arr imports (works for Usenet users as well)
1. Set `QUEUECLEANER__ENABLED` to `true`.
2. Set `QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES` to a desired value.
3. Optionally set failed import message patterns to ignore using `QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__<NUMBER>`.
4. Set `DOWNLOAD_CLIENT` to `none`(works only for usenet) or `disabled` (works for both usenet and torrent).
> [!IMPORTANT]
> When `DOWNLOAD_CLIENT=disabled`, no other action involving a download client would work (e.g. content blocking, removing stalled downloads, excluding private trackers).
>
> When the download client is set to `disabled`, the queue cleaner will be able to remove items that are failed to be imported even if there is no download client configured. This means that all downloads, including private ones, will be completely removed.
>
> Setting `DOWNLOAD_CLIENT=disabled` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
## Usage
### <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/docker.svg" height="20" style="vertical-align: middle;"> <span style="vertical-align: middle;">Docker</span>
### **Environment variables**
**Jump to:**
- [General settings](variables.md#general-settings)
- [Queue Cleaner settings](variables.md#queue-cleaner-settings)
- [Content Blocker settings](variables.md#content-blocker-settings)
- [Download Cleaner settings](variables.md#download-cleaner-settings)
- [Download Client settings](variables.md#download-client-settings)
- [Arr settings](variables.md#arr-settings)
- [Notification settings](variables.md#notification-settings)
- [Advanced settings](variables.md#advanced-settings)
### Docker compose example
> [!NOTE]
>
> This example contains all settings and should be modified to fit your needs.
```
version: "3.3"
services:
cleanuperr:
image: ghcr.io/flmorg/cleanuperr:latest
restart: unless-stopped
volumes:
- ./cleanuperr/logs:/var/logs
- ./cleanuperr/ignored.txt:/ignored.txt
environment:
# general settings
- TZ=America/New_York
- DRY_RUN=false
- HTTP_MAX_RETRIES=0
- HTTP_TIMEOUT=100
# logging
- LOGGING__LOGLEVEL=Information
- LOGGING__FILE__ENABLED=false
- LOGGING__FILE__PATH=/var/logs/
- LOGGING__ENHANCED=true
# job triggers
- TRIGGERS__QUEUECLEANER=0 0/5 * * * ?
- TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ?
- TRIGGERS__DOWNLOADCLEANER=0 0 * * * ?
# queue cleaner
- QUEUECLEANER__ENABLED=true
- QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
- QUEUECLEANER__RUNSEQUENTIALLY=true
# failed imports
- QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=5
- QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE=false
- QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE=false
- QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0=title mismatch
- QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1=manual import required
# stalled downloads
- QUEUECLEANER__STALLED_MAX_STRIKES=5
- QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS=false
- QUEUECLEANER__STALLED_IGNORE_PRIVATE=false
- QUEUECLEANER__STALLED_DELETE_PRIVATE=false
# slow downloads
- QUEUECLEANER__SLOW_MAX_STRIKES=5
- QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS=true
- QUEUECLEANER__SLOW_IGNORE_PRIVATE=false
- QUEUECLEANER__SLOW_DELETE_PRIVATE=false
- QUEUECLEANER__SLOW_MIN_SPEED=1MB
- QUEUECLEANER__SLOW_MAX_TIME=20
- QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE=60GB
# content blocker
- CONTENTBLOCKER__ENABLED=true
- CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH=/ignored.txt
- CONTENTBLOCKER__IGNORE_PRIVATE=false
- CONTENTBLOCKER__DELETE_PRIVATE=false
# download cleaner
- DOWNLOADCLEANER__ENABLED=true
- DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
- DOWNLOADCLEANER__DELETE_PRIVATE=false
# categories to seed until max ratio or min seed time has been reached
- DOWNLOADCLEANER__CATEGORIES__0__NAME=tv-sonarr
- DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO=-1
- DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME=0
- DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME=240
- DOWNLOADCLEANER__CATEGORIES__1__NAME=radarr
- DOWNLOADCLEANER__CATEGORIES__1__MAX_RATIO=-1
- DOWNLOADCLEANER__CATEGORIES__1__MIN_SEED_TIME=0
- DOWNLOADCLEANER__CATEGORIES__1__MAX_SEED_TIME=240
- DOWNLOAD_CLIENT=none
# OR
# - DOWNLOAD_CLIENT=disabled
# OR
# - DOWNLOAD_CLIENT=qBittorrent
# - QBITTORRENT__URL=http://localhost:8080
# - QBITTORRENT__URL_BASE=myCustomPath
# - QBITTORRENT__USERNAME=user
# - QBITTORRENT__PASSWORD=pass
# OR
# - DOWNLOAD_CLIENT=deluge
# - DELUGE__URL_BASE=myCustomPath
# - DELUGE__URL=http://localhost:8112
# - DELUGE__PASSWORD=testing
# OR
# - DOWNLOAD_CLIENT=transmission
# - TRANSMISSION__URL=http://localhost:9091
# - TRANSMISSION__URL_BASE=myCustomPath
# - TRANSMISSION__USERNAME=test
# - TRANSMISSION__PASSWORD=testing
- SONARR__ENABLED=true
- SONARR__SEARCHTYPE=Episode
- SONARR__BLOCK__TYPE=blacklist
- SONARR__BLOCK__PATH=https://example.com/path/to/file.txt
- SONARR__INSTANCES__0__URL=http://localhost:8989
- SONARR__INSTANCES__0__APIKEY=secret1
- SONARR__INSTANCES__1__URL=http://localhost:8990
- SONARR__INSTANCES__1__APIKEY=secret2
- RADARR__ENABLED=true
- RADARR__BLOCK__TYPE=blacklist
- RADARR__BLOCK__PATH=https://example.com/path/to/file.txt
- RADARR__INSTANCES__0__URL=http://localhost:7878
- RADARR__INSTANCES__0__APIKEY=secret3
- RADARR__INSTANCES__1__URL=http://localhost:7879
- RADARR__INSTANCES__1__APIKEY=secret4
- LIDARR__ENABLED=true
- LIDARR__BLOCK__TYPE=blacklist
- LIDARR__BLOCK__PATH=https://example.com/path/to/file.txt
- LIDARR__INSTANCES__0__URL=http://radarr:8686
- LIDARR__INSTANCES__0__APIKEY=secret5
- LIDARR__INSTANCES__1__URL=http://radarr:8687
- LIDARR__INSTANCES__1__APIKEY=secret6
- NOTIFIARR__ON_IMPORT_FAILED_STRIKE=true
- NOTIFIARR__ON_STALLED_STRIKE=true
- NOTIFIARR__ON_SLOW_STRIKE=true
- NOTIFIARR__ON_QUEUE_ITEM_DELETED=true
- NOTIFIARR__ON_DOWNLOAD_CLEANED=true
- NOTIFIARR__API_KEY=notifiarr_secret
- NOTIFIARR__CHANNEL_ID=discord_channel_id
- APPRISE__ON_IMPORT_FAILED_STRIKE=true
- APPRISE__ON_STALLED_STRIKE=true
- APPRISE__ON_SLOW_STRIKE=true
- APPRISE__ON_QUEUE_ITEM_DELETED=true
- APPRISE__ON_DOWNLOAD_CLEANED=true
- APPRISE__URL=http://apprise:8000
- APPRISE__KEY=myConfigKey
```
### <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/windows.svg" height="20" style="vertical-align: middle;"> <span style="vertical-align: middle;">Windows</span>
1. Download the zip file from [releases](https://github.com/flmorg/cleanuperr/releases).
2. Extract the zip file into `C:\example\directory`.
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](#environment-variables).
4. Execute `cleanuperr.exe`.
> [!TIP]
> ### Run as a Windows Service
> 1. Download latest nssm build from `https://nssm.cc/builds`.
> 2. Unzip `nssm.exe` in `C:\example\directory`.
> 3. Open a terminal with Administrator rights and execute these commands:
> ```
> nssm.exe install Cleanuperr "C:\example\directory\cleanuperr.exe"
> nssm.exe set Cleanuperr AppDirectory "C:\example\directory\"
> nssm.exe set Cleanuperr AppStdout "C:\example\directory\cleanuperr.log"
> nssm.exe set Cleanuperr AppStderr "C:\example\directory\cleanuperr.crash.log"
> nssm.exe set Cleanuperr AppRotateFiles 1
> nssm.exe set Cleanuperr AppRotateOnline 1
> nssm.exe set Cleanuperr AppRotateBytes 10485760
> nssm.exe set Cleanuperr AppRotateFiles 10
> nssm.exe set Cleanuperr Start SERVICE_AUTO_START
> nssm.exe start Cleanuperr
> ```
### <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/linux.svg" height="20" style="vertical-align: middle;"> <span style="vertical-align: middle;">Linux</span>
1. Download the zip file from [releases](https://github.com/flmorg/cleanuperr/releases).
2. Extract the zip file into `/example/directory`.
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](#environment-variables).
4. Open a terminal and execute these commands:
```
cd /example/directory
chmod +x cleanuperr
./cleanuperr
```
### <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/apple.svg" height="20" style="vertical-align: middle;"> <span style="vertical-align: middle;">MacOS</span>
1. Download the zip file from [releases](https://github.com/flmorg/cleanuperr/releases).
2. Extract the zip file into `/example/directory`.
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](#environment-variables).
4. Open a terminal and execute these commands:
```
cd /example/directory
chmod +x cleanuperr
./cleanuperr
```
> [!IMPORTANT]
> Some people have experienced problems when trying to execute cleanuperr on MacOS because the system actively blocked the file for not being signed.
> As per [this comment](https://stackoverflow.com/a/77907937), you may need to also execute this command:
> ```
> codesign --sign - --force --preserve-metadata=entitlements,requirements,flags,runtime /example/directory/cleanuperr
> ```
### <img src="https://raw.githubusercontent.com/FortAwesome/Font-Awesome/6.x/svgs/brands/freebsd.svg" height="20" style="vertical-align: middle;"> <span style="vertical-align: middle;">FreeBSD</span>
1. Installation:
```
# install dependencies
pkg install -y git icu libinotify libunwind wget
# set up the dotnet SDK
cd ~
wget -q https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/dotnet-sdk-9.0.104-freebsd-x64.tar.gz
export DOTNET_ROOT=$(pwd)/.dotnet
mkdir -p "$DOTNET_ROOT" && tar zxf dotnet-sdk-9.0.104-freebsd-x64.tar.gz -C "$DOTNET_ROOT"
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
# download NuGet dependencies
mkdir -p /tmp/nuget
wget -q -P /tmp/nuget/ https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/Microsoft.AspNetCore.App.Runtime.freebsd-x64.9.0.3.nupkg
wget -q -P /tmp/nuget/ https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/Microsoft.NETCore.App.Host.freebsd-x64.9.0.3.nupkg
wget -q -P /tmp/nuget/ https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/Microsoft.NETCore.App.Runtime.freebsd-x64.9.0.3.nupkg
# add NuGet source
dotnet nuget add source /tmp/nuget --name tmp
# add GitHub NuGet source
# a PAT (Personal Access Token) can be generated here https://github.com/settings/tokens
dotnet nuget add source --username <YOUR_USERNAME> --password <YOUR_PERSONAL_ACCESS_TOKEN> --store-password-in-clear-text --name flmorg https://nuget.pkg.github.com/flmorg/index.json
```
2. Building:
```
# clone the project
git clone https://github.com/flmorg/cleanuperr.git
cd cleanuperr
# build and publish the app
dotnet publish code/Executable/Executable.csproj -c Release --self-contained -o artifacts /p:PublishSingleFile=true
# move the files to permanent destination
mv artifacts/cleanuperr /example/directory/
mv artifacts/appsettings.json /example/directory/
```
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](#environment-variables).
4. Run the app:
```
cd /example/directory
chmod +x cleanuperr
./cleanuperr
```
Docs can be found [here](https://flmorg.github.io/cleanuperr/).
# Credits
Special thanks for inspiration go to:

20
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

41
docs/README.md Normal file
View File

@@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@@ -0,0 +1,24 @@
---
sidebar_position: 1
---
import { Warning } from '@site/src/components/Admonition';
# Cleanuperr
Cleanuperr is a tool for automating the cleanup of unwanted or blocked files in Sonarr, Radarr, and supported download clients like qBittorrent. It removes incomplete or blocked downloads, updates queues, and enforces blacklists or whitelists to manage file selection. After removing blocked content, cleanuperr can also trigger a search to replace the deleted shows/movies.
Cleanuperr was created primarily to address malicious files, such as `*.lnk` or `*.zipx`, that were getting stuck in Sonarr/Radarr and required manual intervention. Some of the reddit posts that made cleanuperr come to life can be found [here](https://www.reddit.com/r/sonarr/comments/1gqnx16/psa_sonarr_downloaded_a_virus/), [here](https://www.reddit.com/r/sonarr/comments/1gqwklr/sonar_downloaded_a_mkv_file_which_looked_like_a/), [here](https://www.reddit.com/r/sonarr/comments/1gpw2wa/downloaded_waiting_to_import/) and [here](https://www.reddit.com/r/sonarr/comments/1gpi344/downloads_not_importing_no_files_found/).
<Warning>
Because this tool is actively developed and still a work in progress, using the `latest` Docker tag may result in breaking changes.
Join the Discord server if you want to reach out to me quickly (or just stay updated on new releases) so we can squash those pesky bugs together: https://discord.gg/sWggpnmGNY
</Warning>
## Naming choice
I've had people asking why it's `cleanuperr` and not `cleanuparr` and that I should change it. This name was intentional.
I've seen a few discussions on this type of naming and I've decided that I didn't deserve the `arr` moniker since `cleanuperr` is not a fork of `NZB.Drone` and it does not have any affiliation with the arrs. I still wanted to keep the naming style close enough though, to suggest a correlation between them.

View File

@@ -0,0 +1,13 @@
---
sidebar_position: 2
---
# Supported apps
Only the **latest versions** of the following apps are supported, or earlier versions that have the same API as the latest version:
- qBittorrent
- Deluge
- Transmission
- Sonarr
- Radarr
- Lidarr

View File

@@ -0,0 +1,35 @@
---
sidebar_position: 4
---
# How it works
This is a detailed explanation of how the recurring cleanup jobs work.
#### 1. **Content blocker** will:
- Run every 5 minutes (or configured cron).
- Process all items in the *arr queue.
- Find the corresponding item from the download client for each queue item.
- Mark the files that were found in the queue as **unwanted/skipped** if:
- They **are listed in the blacklist**, or
- They **are not included in the whitelist**.
- If **all files** of a download **are unwanted**:
- It will be removed from the *arr's queue and blocked.
- It will be deleted from the download client.
- A new search will be triggered for the *arr item.
#### 2. **Queue cleaner** will:
- Run every 5 minutes (or configured cron, or right after `Content Blocker`).
- Process all items in the *arr queue.
- Check each queue item if it is **stalled (download speed is 0)**, **stuck in metadata downloading**, **failed to be imported** or **slow**.
- If it is, the item receives a **strike** and will continue to accumulate strikes every time it meets any of these conditions.
- Check each queue item if it meets one of the following condition in the download client:
- **Marked as completed, but 0 bytes have been downloaded** (due to files being blocked by qBittorrent or the **content blocker**).
- All associated files are marked as **unwanted/skipped/do not download**.
- If the item **DOES NOT** match the above criteria, it will be skipped.
- If the item **DOES** match the criteria or has received the **maximum number of strikes**:
- It will be removed from the *arr's queue and blocked.
- It will be deleted from the download client.
- A new search will be triggered for the *arr item.
#### 3. **Download cleaner** will:
- Run every hour (or configured cron).
- Automatically clean up downloads that have been seeding for a certain amount of time.

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 1
---
import GeneralSettings from '@site/src/components/configuration/GeneralSettings';
# General Settings
These are the general configuration settings that apply to the entire application.
<GeneralSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Configuration",
"position": 6,
"link": {
"type": "generated-index",
"description": "This page provides documentation for all the environment variables and settings used in the application."
}
}

View File

@@ -0,0 +1,18 @@
---
sidebar_position: 1
---
import SonarrSettings from '@site/src/components/configuration/arrs/SonarrSettings';
import { Note } from '@site/src/components/Admonition';
# Sonarr Settings
<Note>
Multiple instances can be specified for each *arr using this format, where `<NUMBER>` starts from 0:
```yaml
<ARR>__INSTANCES__<NUMBER>__URL
<ARR>__INSTANCES__<NUMBER>__APIKEY
```
</Note>
<SonarrSettings/>

View File

@@ -0,0 +1,18 @@
---
sidebar_position: 2
---
import RadarrSettings from '@site/src/components/configuration/arrs/RadarrSettings';
import { Note } from '@site/src/components/Admonition';
# Radarr Settings
<Note>
Multiple instances can be specified for each *arr using this format, where `<NUMBER>` starts from 0:
```yaml
<ARR>__INSTANCES__<NUMBER>__URL
<ARR>__INSTANCES__<NUMBER>__APIKEY
```
</Note>
<RadarrSettings/>

View File

@@ -0,0 +1,18 @@
---
sidebar_position: 3
---
import LidarrSettings from '@site/src/components/configuration/arrs/LidarrSettings';
import { Note } from '@site/src/components/Admonition';
# Lidarr Settings
<Note>
Multiple instances can be specified for each *arr using this format, where `<NUMBER>` starts from 0:
```yaml
<ARR>__INSTANCES__<NUMBER>__URL
<ARR>__INSTANCES__<NUMBER>__APIKEY
```
</Note>
<LidarrSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Arrs settings",
"position": 6,
"link": {
"type": "generated-index",
"description": "Servarr settings."
}
}

View File

@@ -0,0 +1,19 @@
---
sidebar_position: 1
---
import ContentBlockerGeneralSettings from '@site/src/components/configuration/content-blocker/ContentBlockerGeneralSettings';
# General Settings
These settings control the general behavior of the Content Blocker functionality.
These environment variables are needed to enable the Content Blocker functionality:
- [SONARR__BLOCK__TYPE](/docs/configuration/arrs/sonarr?SONARR__BLOCK__TYPE) (if Sonarr is enabled)
- [SONARR__BLOCK__PATH](/docs/configuration/arrs/sonarr?SONARR__BLOCK__PATH) (if Sonarr is enabled)
- [RADARR__BLOCK__TYPE](/docs/configuration/arrs/radarr?RADARR__BLOCK__TYPE) (if Radarr is enabled)
- [RADARR__BLOCK__PATH](/docs/configuration/arrs/radarr?RADARR__BLOCK__PATH) (if Radarr is enabled)
- [LIDARR__BLOCK__TYPE](/docs/configuration/arrs/lidarr?LIDARR__BLOCK__TYPE) (if Lidarr is enabled)
- [LIDARR__BLOCK__PATH](/docs/configuration/arrs/lidarr?LIDARR__BLOCK__PATH) (if Lidarr is enabled)
<ContentBlockerGeneralSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Content Blocker",
"position": 2,
"link": {
"type": "generated-index",
"description": "Settings for the Content Blocker functionality."
}
}

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 1
---
import DownloadCleanerGeneralSettings from '@site/src/components/configuration/download-cleaner/DownloadCleanerGeneralSettings';
# General Settings
These settings control the basic functionality of the Download Cleaner.
<DownloadCleanerGeneralSettings/>

View File

@@ -0,0 +1,26 @@
---
sidebar_position: 2
---
import DownloadCleanerCleanupSettings from '@site/src/components/configuration/download-cleaner/DownloadCleanerCleanupSettings';
import { Note } from '@site/src/components/Admonition';
# Cleanup Settings
These settings control how the Download Cleaner handles different categories of downloads that need to be removed.
<Note>
A download is cleaned when both `MAX_RATIO` and `MIN_SEED_TIME` or just `MAX_SEED_TIME` is reached.
</Note>
<Note>
Multiple categories can be specified using this format, where `<NUMBER>` starts from `0`:
```yaml
DOWNLOADCLEANER__CATEGORIES__<NUMBER>__NAME
DOWNLOADCLEANER__CATEGORIES__<NUMBER>__MAX_RATIO
DOWNLOADCLEANER__CATEGORIES__<NUMBER>__MIN_SEED_TIME
DOWNLOADCLEANER__CATEGORIES__<NUMBER>__MAX_SEED_TIME
```
</Note>
<DownloadCleanerCleanupSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Download Cleaner",
"position": 3,
"link": {
"type": "generated-index",
"description": "Configure the Download Cleaner to automatically clean up downloads that have been seeding for a certain amount of time."
}
}

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 1
---
import DownloadClientSettings from '@site/src/components/configuration/download-client/DownloadClientSettings';
# Download Client Settings
These settings control how Cleanuperr interacts with your download client.
<DownloadClientSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Download Client",
"position": 4,
"link": {
"type": "generated-index",
"description": "Configure the download client settings for Cleanuperr."
}
}

View File

@@ -0,0 +1,144 @@
import { Note } from '@site/src/components/Admonition';
# Docker compose
<Note>
**This example contains all settings and should be modified to fit your needs.**
</Note>
```
services:
cleanuperr:
image: ghcr.io/flmorg/cleanuperr:latest
restart: unless-stopped
volumes:
- ./cleanuperr/logs:/var/logs
- ./cleanuperr/ignored.txt:/ignored.txt
environment:
# general settings
- TZ=America/New_York
- DRY_RUN=false
- HTTP_MAX_RETRIES=0
- HTTP_TIMEOUT=100
# logging
- LOGGING__LOGLEVEL=Information
- LOGGING__FILE__ENABLED=false
- LOGGING__FILE__PATH=/var/logs/
- LOGGING__ENHANCED=true
# job triggers
- TRIGGERS__QUEUECLEANER=0 0/5 * * * ?
- TRIGGERS__CONTENTBLOCKER=0 0/5 * * * ?
- TRIGGERS__DOWNLOADCLEANER=0 0 * * * ?
# queue cleaner
- QUEUECLEANER__ENABLED=true
- QUEUECLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
- QUEUECLEANER__RUNSEQUENTIALLY=true
# failed imports
- QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES=5
- QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE=false
- QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE=false
- QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0=title mismatch
- QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1=manual import required
# stalled downloads
- QUEUECLEANER__STALLED_MAX_STRIKES=5
- QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS=false
- QUEUECLEANER__STALLED_IGNORE_PRIVATE=false
- QUEUECLEANER__STALLED_DELETE_PRIVATE=false
# slow downloads
- QUEUECLEANER__SLOW_MAX_STRIKES=5
- QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS=true
- QUEUECLEANER__SLOW_IGNORE_PRIVATE=false
- QUEUECLEANER__SLOW_DELETE_PRIVATE=false
- QUEUECLEANER__SLOW_MIN_SPEED=1MB
- QUEUECLEANER__SLOW_MAX_TIME=20
- QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE=60GB
# content blocker
- CONTENTBLOCKER__ENABLED=true
- CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH=/ignored.txt
- CONTENTBLOCKER__IGNORE_PRIVATE=false
- CONTENTBLOCKER__DELETE_PRIVATE=false
# download cleaner
- DOWNLOADCLEANER__ENABLED=true
- DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH=/ignored.txt
- DOWNLOADCLEANER__DELETE_PRIVATE=false
# categories to seed until max ratio or min seed time has been reached
- DOWNLOADCLEANER__CATEGORIES__0__NAME=tv-sonarr
- DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO=-1
- DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME=0
- DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME=240
- DOWNLOADCLEANER__CATEGORIES__1__NAME=radarr
- DOWNLOADCLEANER__CATEGORIES__1__MAX_RATIO=-1
- DOWNLOADCLEANER__CATEGORIES__1__MIN_SEED_TIME=0
- DOWNLOADCLEANER__CATEGORIES__1__MAX_SEED_TIME=240
- DOWNLOAD_CLIENT=none
# OR
# - DOWNLOAD_CLIENT=disabled
# OR
# - DOWNLOAD_CLIENT=qBittorrent
# - QBITTORRENT__URL=http://localhost:8080
# - QBITTORRENT__URL_BASE=myCustomPath
# - QBITTORRENT__USERNAME=user
# - QBITTORRENT__PASSWORD=pass
# OR
# - DOWNLOAD_CLIENT=deluge
# - DELUGE__URL_BASE=myCustomPath
# - DELUGE__URL=http://localhost:8112
# - DELUGE__PASSWORD=testing
# OR
# - DOWNLOAD_CLIENT=transmission
# - TRANSMISSION__URL=http://localhost:9091
# - TRANSMISSION__URL_BASE=myCustomPath
# - TRANSMISSION__USERNAME=test
# - TRANSMISSION__PASSWORD=testing
- SONARR__ENABLED=true
- SONARR__SEARCHTYPE=Episode
- SONARR__BLOCK__TYPE=blacklist
- SONARR__BLOCK__PATH=https://example.com/path/to/file.txt
- SONARR__INSTANCES__0__URL=http://localhost:8989
- SONARR__INSTANCES__0__APIKEY=secret1
- SONARR__INSTANCES__1__URL=http://localhost:8990
- SONARR__INSTANCES__1__APIKEY=secret2
- RADARR__ENABLED=true
- RADARR__BLOCK__TYPE=blacklist
- RADARR__BLOCK__PATH=https://example.com/path/to/file.txt
- RADARR__INSTANCES__0__URL=http://localhost:7878
- RADARR__INSTANCES__0__APIKEY=secret3
- RADARR__INSTANCES__1__URL=http://localhost:7879
- RADARR__INSTANCES__1__APIKEY=secret4
- LIDARR__ENABLED=true
- LIDARR__BLOCK__TYPE=blacklist
- LIDARR__BLOCK__PATH=https://example.com/path/to/file.txt
- LIDARR__INSTANCES__0__URL=http://radarr:8686
- LIDARR__INSTANCES__0__APIKEY=secret5
- LIDARR__INSTANCES__1__URL=http://radarr:8687
- LIDARR__INSTANCES__1__APIKEY=secret6
- NOTIFIARR__ON_IMPORT_FAILED_STRIKE=true
- NOTIFIARR__ON_STALLED_STRIKE=true
- NOTIFIARR__ON_SLOW_STRIKE=true
- NOTIFIARR__ON_QUEUE_ITEM_DELETED=true
- NOTIFIARR__ON_DOWNLOAD_CLEANED=true
- NOTIFIARR__API_KEY=notifiarr_secret
- NOTIFIARR__CHANNEL_ID=discord_channel_id
- APPRISE__ON_IMPORT_FAILED_STRIKE=true
- APPRISE__ON_STALLED_STRIKE=true
- APPRISE__ON_SLOW_STRIKE=true
- APPRISE__ON_QUEUE_ITEM_DELETED=true
- APPRISE__ON_DOWNLOAD_CLEANED=true
- APPRISE__URL=http://apprise:8000
- APPRISE__KEY=myConfigKey
```

View File

@@ -0,0 +1,166 @@
import { Note } from '@site/src/components/Admonition';
# Configuration file example (when not using Docker)
<Note>
**This example contains all settings and should be modified to fit your needs.**
</Note>
```
{
"TZ": "America/New_York",
"DRY_RUN": true,
"HTTP_MAX_RETRIES": 0,
"HTTP_TIMEOUT": 10,
"Logging": {
"LogLevel": "Information",
"Enhanced": true,
"File": {
"Enabled": false,
"Path": "/var/logs"
}
},
"Triggers": {
"QueueCleaner": "0 0/5 * * * ?",
"ContentBlocker": "0 0/5 * * * ?",
"DownloadCleaner": "0 0 * * * ?"
},
"QueueCleaner": {
"Enabled": true,
"RunSequentially": true,
"IGNORED_DOWNLOADS_PATH": "/ignored.txt",
"IMPORT_FAILED_MAX_STRIKES": 5,
"IMPORT_FAILED_IGNORE_PRIVATE": false,
"IMPORT_FAILED_DELETE_PRIVATE": false,
"IMPORT_FAILED_IGNORE_PATTERNS": [
"title mismatch",
"manual import required"
],
"STALLED_MAX_STRIKES": 5,
"STALLED_RESET_STRIKES_ON_PROGRESS": true,
"STALLED_IGNORE_PRIVATE": false,
"STALLED_DELETE_PRIVATE": false,
"SLOW_MAX_STRIKES": 5,
"SLOW_RESET_STRIKES_ON_PROGRESS": true,
"SLOW_IGNORE_PRIVATE": false,
"SLOW_DELETE_PRIVATE": false,
"SLOW_MIN_SPEED": "1MB",
"SLOW_MAX_TIME": 20,
"SLOW_IGNORE_ABOVE_SIZE": "60GB"
},
"ContentBlocker": {
"Enabled": true,
"IGNORE_PRIVATE": false,
"DELETE_PRIVATE": false,
"IGNORED_DOWNLOADS_PATH": "/ignored.txt"
},
"DownloadCleaner": {
"Enabled": false,
"DELETE_PRIVATE": false,
"CATEGORIES": [
{
"Name": "tv-sonarr",
"MAX_RATIO": 1,
"MIN_SEED_TIME": 0,
"MAX_SEED_TIME": 240
},
{
"Name": "radarr",
"MAX_RATIO": 1,
"MIN_SEED_TIME": 0,
"MAX_SEED_TIME": 240
}
],
"IGNORED_DOWNLOADS_PATH": "/ignored.txt"
},
"DOWNLOAD_CLIENT": "none",
"qBittorrent": {
"Url": "http://localhost:8080",
"URL_BASE": "myCustomPath",
"Username": "user",
"Password": "pass"
},
"Deluge": {
"Url": "http://localhost:8112",
"URL_BASE": "myCustomPath",
"Password": "pass"
},
"Transmission": {
"Url": "http://localhost:9091",
"URL_BASE": "myCustomPath",
"Username": "user",
"Password": "pass"
},
"Sonarr": {
"Enabled": true,
"SearchType": "Episode",
"Block": {
"Type": "blacklist",
"Path": "https://example.com/path/to/file.txt"
},
"Instances": [
{
"Url": "http://localhost:8989",
"ApiKey": "sonarrSecret1"
},
{
"Url": "http://localhost:8990",
"ApiKey": "sonarrSecret2"
},
]
},
"Radarr": {
"Enabled": true,
"Block": {
"Type": "blacklist",
"Path": "https://example.com/path/to/file.txt"
},
"Instances": [
{
"Url": "http://localhost:7878",
"ApiKey": "sonarrSecret1"
},
{
"Url": "http://localhost:7879",
"ApiKey": "sonarrSecret2"
}
]
},
"Lidarr": {
"Enabled": true,
"Block": {
"Type": "blacklist",
"Path": "https://example.com/path/to/file.txt"
},
"Instances": [
{
"Url": "http://localhost:8686",
"ApiKey": "lidarrSecret1"
},
{
"Url": "http://localhost:8687",
"ApiKey": "lidarrSecret2"
}
]
},
"Notifiarr": {
"ON_IMPORT_FAILED_STRIKE": true,
"ON_STALLED_STRIKE": true,
"ON_SLOW_STRIKE": true,
"ON_QUEUE_ITEM_DELETED": true,
"ON_DOWNLOAD_CLEANED": true,
"API_KEY": "notifiarr_secret",
"CHANNEL_ID": "discord_channel_id"
},
"Apprise": {
"ON_IMPORT_FAILED_STRIKE": true,
"ON_STALLED_STRIKE": true,
"ON_SLOW_STRIKE": true,
"ON_QUEUE_ITEM_DELETED": true,
"ON_DOWNLOAD_CLEANED": true,
"URL": "http://localhost:8000",
"KEY": "myConfigKey"
}
}
```

View File

@@ -0,0 +1,7 @@
{
"label": "Configuration examples",
"position": 8,
"link": {
"type": "generated-index"
}
}

View File

@@ -0,0 +1,7 @@
import NotifiarrSettings from '@site/src/components/configuration/notifications/NotifiarrSettings';
# Notifiarr Settings
These settings control how Cleanuperr sends notifications through [Notifiarr](https://notifiarr.com/).
<NotifiarrSettings/>

View File

@@ -0,0 +1,7 @@
import AppriseSettings from '@site/src/components/configuration/notifications/AppriseSettings';
# Apprise Settings
These settings control how Cleanuperr sends notifications through [Apprise](https://github.com/caronc/apprise-api).
<AppriseSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Notifications",
"position": 7,
"link": {
"type": "generated-index",
"description": "Settings for receiving notifications."
}
}

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 1
---
import QueueCleanerGeneralSettings from '@site/src/components/configuration/queue-cleaner/QueueCleanerGeneralSettings';
# General Settings
These settings control the general behavior of the Queue Cleaner functionality.
<QueueCleanerGeneralSettings/>

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 2
---
import QueueCleanerImportFailedSettings from '@site/src/components/configuration/queue-cleaner/QueueCleanerImportFailedSettings';
# Import Failed Settings
These settings control how the Queue Cleaner handles failed imports.
<QueueCleanerImportFailedSettings/>

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 3
---
import QueueCleanerStalledSettings from '@site/src/components/configuration/queue-cleaner/QueueCleanerStalledSettings';
# Stalled Downloads Settings
These settings control how the Queue Cleaner handles stalled downloads.
<QueueCleanerStalledSettings/>

View File

@@ -0,0 +1,11 @@
---
sidebar_position: 4
---
import QueueCleanerSlowSettings from '@site/src/components/configuration/queue-cleaner/QueueCleanerSlowSettings';
# Slow Downloads Settings
These settings control how the Queue Cleaner handles slow downloads.
<QueueCleanerSlowSettings/>

View File

@@ -0,0 +1,8 @@
{
"label": "Queue Cleaner",
"position": 1,
"link": {
"type": "generated-index",
"description": "Settings for the Queue Cleaner functionality."
}
}

View File

@@ -0,0 +1,14 @@
import { Note } from '@site/src/components/Admonition';
# Quick start
1. **Docker (Recommended)**
Pull the Docker image from `ghcr.io/flmorg/cleanuperr:latest`.
2. **Unraid (for Unraid users)**
Use the Unraid Community App.
3. **Manual Installation (if you're not using Docker)**
Go to [Windows](/docs/installation/windows), [Linux](/docs/installation/linux) or [MacOS](/docs/installation/macos).
<Note>
Refer to the [Configuration](/docs/category/configuration) section for detailed configuration instructions.
</Note>

View File

@@ -0,0 +1,31 @@
import { Note } from '@site/src/components/Admonition';
# Windows
<Note>
The preferred method of installation method is using Docker.
</Note>
1. Download the zip file from [releases](https://github.com/flmorg/cleanuperr/releases).
2. Extract the zip file into `C:\example\directory`.
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](/docs/category/configuration).
4. Execute `cleanuperr.exe`.
<Note>
### Run as a Windows Service
1. Download latest nssm build from `https://nssm.cc/builds`.
2. Unzip `nssm.exe` in `C:\example\directory`.
3. Open a terminal with Administrator rights and execute these commands:
```
nssm.exe install Cleanuperr "C:\example\directory\cleanuperr.exe"
nssm.exe set Cleanuperr AppDirectory "C:\example\directory\"
nssm.exe set Cleanuperr AppStdout "C:\example\directory\cleanuperr.log"
nssm.exe set Cleanuperr AppStderr "C:\example\directory\cleanuperr.crash.log"
nssm.exe set Cleanuperr AppRotateFiles 1
nssm.exe set Cleanuperr AppRotateOnline 1
nssm.exe set Cleanuperr AppRotateBytes 10485760
nssm.exe set Cleanuperr AppRotateFiles 10
nssm.exe set Cleanuperr Start SERVICE_AUTO_START
nssm.exe start Cleanuperr
```
</Note>

View File

@@ -0,0 +1,17 @@
import { Note } from '@site/src/components/Admonition';
# Linux
<Note>
The preferred method of installation method is using Docker.
</Note>
1. Download the zip file from [releases](https://github.com/flmorg/cleanuperr/releases).
2. Extract the zip file into `/example/directory`.
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](/docs/category/configuration).
4. Open a terminal and execute these commands:
```
cd /example/directory
chmod +x cleanuperr
./cleanuperr
```

View File

@@ -0,0 +1,25 @@
import { Important, Note } from '@site/src/components/Admonition';
# MacOS
<Note>
The preferred method of installation method is using Docker.
</Note>
1. Download the zip file from [releases](https://github.com/flmorg/cleanuperr/releases).
2. Extract the zip file into `/example/directory`.
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](/docs/category/configuration).
4. Open a terminal and execute these commands:
```
cd /example/directory
chmod +x cleanuperr
./cleanuperr
```
<Important>
Some people have experienced problems when trying to execute cleanuperr on MacOS because the system actively blocked the file for not being signed.
As per [this comment](https://stackoverflow.com/a/77907937), you may need to also execute this command:
```
codesign --sign - --force --preserve-metadata=entitlements,requirements,flags,runtime /example/directory/cleanuperr
```
</Important>

View File

@@ -0,0 +1,53 @@
import { Note } from '@site/src/components/Admonition';
# FreeBSD
<Note>
The preferred method of installation method is using Docker.
</Note>
1. Installation:
```
# install dependencies
pkg install -y git icu libinotify libunwind wget
# set up the dotnet SDK
cd ~
wget -q https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/dotnet-sdk-9.0.104-freebsd-x64.tar.gz
export DOTNET_ROOT=$(pwd)/.dotnet
mkdir -p "$DOTNET_ROOT" && tar zxf dotnet-sdk-9.0.104-freebsd-x64.tar.gz -C "$DOTNET_ROOT"
export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
# download NuGet dependencies
mkdir -p /tmp/nuget
wget -q -P /tmp/nuget/ https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/Microsoft.AspNetCore.App.Runtime.freebsd-x64.9.0.3.nupkg
wget -q -P /tmp/nuget/ https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/Microsoft.NETCore.App.Host.freebsd-x64.9.0.3.nupkg
wget -q -P /tmp/nuget/ https://github.com/Thefrank/dotnet-freebsd-crossbuild/releases/download/v9.0.104-amd64-freebsd-14/Microsoft.NETCore.App.Runtime.freebsd-x64.9.0.3.nupkg
# add NuGet source
dotnet nuget add source /tmp/nuget --name tmp
# add GitHub NuGet source
# a PAT (Personal Access Token) can be generated here https://github.com/settings/tokens
dotnet nuget add source --username <YOUR_USERNAME> --password <YOUR_PERSONAL_ACCESS_TOKEN> --store-password-in-clear-text --name flmorg https://nuget.pkg.github.com/flmorg/index.json
```
2. Building:
```
# clone the project
git clone https://github.com/flmorg/cleanuperr.git
cd cleanuperr
# build and publish the app
dotnet publish code/Executable/Executable.csproj -c Release --self-contained -o artifacts /p:PublishSingleFile=true
# move the files to permanent destination
mv artifacts/cleanuperr /example/directory/
mv artifacts/appsettings.json /example/directory/
```
3. Edit **appsettings.json**. The paths from this json file correspond with the docker env vars, as described [here](/docs/category/configuration).
4. Run the app:
```
cd /example/directory
chmod +x cleanuperr
./cleanuperr
```

View File

@@ -0,0 +1,7 @@
{
"label": "Installation",
"position": 5,
"link": {
"type": "generated-index",
}
}

View File

@@ -0,0 +1,15 @@
import { Note } from '@site/src/components/Admonition';
# Using qBit's built-in blacklist (torrent)
1. Go to qBittorrent -> Options -> Downloads -> make sure `Excluded file names` is checked -> Paste an exclusion list that you have copied.
- [blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), or
- [permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive), or
- create your own
2. qBittorrent will block files from being downloaded. In the case of malicious content, **nothing is downloaded and the torrent is marked as complete**.
3. Start **cleanuperr** with `QUEUECLEANER__ENABLED` set to `true`.
4. The **Queue Cleaner** will perform a cleanup process as described in the [How it works](/docs/how_it_works) section.
<Note>
This scenario is an example for blocking malicious files. Other features can be included here by configuring the environment variables.
</Note>

View File

@@ -0,0 +1,13 @@
import { Note } from '@site/src/components/Admonition';
# Using Cleanuperr's blocklist feature (torrent)
1. Set both `QUEUECLEANER__ENABLED` and `CONTENTBLOCKER__ENABLED` to `true` in your environment variables.
2. Configure and enable either a **blacklist** or a **whitelist** as described in the [Arr settings](/docs/category/arrs-settings) section.
3. Once configured, cleanuperr will perform the following tasks:
- Execute the **Content Blocker** job, as explained in the [How it works](/docs/how_it_works) section.
- Execute the **Queue Cleaner** job, as explained in the [How it works](/docs/how_it_works) section.
<Note>
This scenario is an example for blocking malicious files. Other features can be included here by configuring the environment variables.
</Note>

View File

@@ -0,0 +1,16 @@
import { Important } from '@site/src/components/Admonition';
# Using Cleanuperr just for failed imports (torrent and usenet)
1. Set `QUEUECLEANER__ENABLED` to `true`.
2. Set `QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES` to a desired value.
3. Optionally set failed import message patterns to ignore using `QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__<NUMBER>`.
4. Set `DOWNLOAD_CLIENT` to `none`(works only for usenet) or `disabled` (works for both usenet and torrent).
<Important>
When `DOWNLOAD_CLIENT=disabled`, no other action involving a download client would work (e.g. content blocking, removing stalled downloads, excluding private trackers).
When the download client is set to `disabled`, the queue cleaner will be able to remove items that are failed to be imported even if there is no download client configured. This means that all downloads, including private ones, will be completely removed.
Setting `DOWNLOAD_CLIENT=disabled` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
</Important>

View File

@@ -0,0 +1,8 @@
{
"label": "Setup scenarios",
"position": 5,
"link": {
"type": "generated-index",
"description": "This page provides documentation on the various setup scenarios for using the application."
}
}

82
docs/docusaurus.config.ts Normal file
View File

@@ -0,0 +1,82 @@
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
const config: Config = {
title: 'Cleanuperr',
tagline: 'Cleaning arrs since \'24.',
favicon: 'img/16.png',
url: 'https://flmorg.github.io',
baseUrl: '/cleanuperr/',
organizationName: 'flmorg',
projectName: 'cleanuperr',
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
colorMode: {
defaultMode: 'dark',
disableSwitch: false,
respectPrefersColorScheme: false,
},
navbar: {
title: 'Cleanuperr',
logo: {
alt: 'Cleanuperr Logo',
src: 'img/cleanuperr.svg',
},
items: [
{
type: 'docSidebar',
sidebarId: 'configurationSidebar',
position: 'left',
label: 'Docs',
activeBasePath: '/docs',
},
{
href: 'https://github.com/flmorg/cleanuperr',
label: 'GitHub',
position: 'right',
},
{
href: 'https://discord.gg/sWggpnmGNY',
label: 'Discord',
position: 'right',
}
],
},
footer: {
style: 'dark',
links: [],
copyright: `Copyright © ${new Date().getFullYear()} Cleanuperr. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
};
export default config;

16684
docs/package-lock.json generated Normal file
View File

File diff suppressed because it is too large Load Diff

48
docs/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.7.0",
"@docusaurus/preset-classic": "3.7.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.7.0",
"@docusaurus/tsconfig": "3.7.0",
"@docusaurus/types": "3.7.0",
"typescript": "~5.6.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}

7
docs/sidebars.ts Normal file
View File

@@ -0,0 +1,7 @@
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
const sidebars: SidebarsConfig = {
configurationSidebar: [{type: 'autogenerated', dirName: '.'}],
};
export default sidebars;

View File

@@ -0,0 +1,33 @@
import React from "react";
export type AdmonitionType = "important" | "warning" | "note";
interface AdmonitionProps {
type: AdmonitionType;
children: React.ReactNode;
}
export default function Admonition({ type, children }: AdmonitionProps) {
return (
<div className={`admonition admonition-${type} alert alert--${type}`}>
<div className="admonition-heading">
<h5>{type.charAt(0).toUpperCase() + type.slice(1)}</h5>
</div>
<div className="admonition-content">
{children}
</div>
</div>
);
}
export function Important({ children }: { children: React.ReactNode }) {
return <Admonition type="important">{children}</Admonition>;
}
export function Warning({ children }: { children: React.ReactNode }) {
return <Admonition type="warning">{children}</Admonition>;
}
export function Note({ children }: { children: React.ReactNode }) {
return <Admonition type="note">{children}</Admonition>;
}

View File

@@ -0,0 +1,72 @@
import type {ReactNode} from 'react';
import ReactMarkdown from 'react-markdown';
import clsx from 'clsx';
import Heading from '@theme/Heading';
import styles from './styles.module.css';
type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
description: ReactNode;
};
const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/cleanuperr.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/cleanuperr.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/cleanuperr.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({title, Svg, description}: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<Heading as="h3">{title}</Heading>
<div>{description}</div>
</div>
</div>
);
}
export default function HomepageFeatures(): ReactNode {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

View File

@@ -0,0 +1,201 @@
import React, { useEffect, useRef } from "react";
import ReactMarkdown from 'react-markdown';
import { useLocation } from "@docusaurus/router";
import Admonition from "../Admonition";
export type DescriptionContent =
| string
| {
type: "code" | "list";
title: string;
content: string | string[];
};
export interface EnvVarProps {
name: string;
description: DescriptionContent[];
type: string;
reference?: string;
required?: boolean | string;
defaultValue: string;
defaultValueComment?: string;
examples?: string[];
acceptedValues?: string[];
children?: React.ReactNode;
notes?: string[];
important?: string[];
warnings?: string[];
}
interface EnvVarsProps {
vars: EnvVarProps[];
}
export default function EnvVars({ vars }: EnvVarsProps) {
return vars.map((env) => <EnvVar key={env.name} env={env} />);
}
function EnvVar({ env }: { env: EnvVarProps }) {
const ref = useRef<HTMLDivElement>(null);
const location = useLocation();
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const queryKeys = Array.from(searchParams.keys());
const matched = queryKeys.find(
(key) => key.toLowerCase() === env.name.toLowerCase()
);
if (matched && ref.current) {
// Scroll to the variable
ref.current.scrollIntoView({ behavior: "smooth", block: "start" });
// Add highlight effect
ref.current.classList.add("env-var-highlight");
setTimeout(() => {
ref.current.classList.add("highlight-removing");
}, 2000);
setTimeout(() => {
ref.current.classList.remove("env-var-highlight", "highlight-removing");
}, 3000);
}
}, [location.search, env.name]);
const renderDescriptionContent = (
content: DescriptionContent,
index: number
) => {
if (typeof content === "string") {
return <ReactMarkdown components={{ p: ({ children }) => <div>{children}</div> }}>{content}</ReactMarkdown>;
}
switch (content.type) {
case "code":
return (
<section>
{content.title && <strong>{content.title}</strong>}
<br />
<pre key={index}>
{content.content}
</pre>
</section>
);
case "list":
return (
<section>
{content.title && <strong>{content.title}</strong>}
<br />
<ul key={index}>
{(Array.isArray(content.content)
? content.content
: [content.content]
).map((item, i) => (
<li key={i}>{item}</li>
))}
</ul>
</section>
);
default:
return null;
}
};
const renderAdmonition = (type: "important" | "warning" | "note", items: string[]) => {
if (!items || items.length === 0) return null;
return (
<Admonition type={type}>
<ul>
{items.map((item, idx) => (
<li key={idx}>
<ReactMarkdown components={{ p: ({ children }) => <>{children}</> }}>
{item}
</ReactMarkdown>
</li>
))}
</ul>
</Admonition>
);
};
return (
<>
<div id={env.name} ref={ref} className="env-var-block">
<h3>
<code>{env.name}</code>
</h3>
{env.description.map((desc, index) =>
renderDescriptionContent(desc, index)
)}
{env.required !== undefined && (
<section>
<strong>Required: </strong>
{typeof env.required === "boolean"
? env.required
? "Yes"
: "No"
: env.required}
</section>
)}
{env.type !== undefined && (
<section>
<strong>Type: </strong>
{env.type}
</section>
)}
{env.defaultValue !== undefined && (
<section>
<strong>Default value: </strong>
<code>{env.defaultValue}</code> {env.defaultValueComment !== undefined && (`(${env.defaultValueComment})`)}
</section>
)}
{env.reference !== undefined && (
<section>
<strong>Reference: </strong>
<ReactMarkdown
components={{
p: ({ children }) => <>{children}</>, // No wrapping <p> tag
}}
>
{`[Quartz.NET](${env.reference})`}
</ReactMarkdown>
</section>
)}
{env.acceptedValues && env.acceptedValues.length > 0 && (
<section>
<strong>Accepted values:</strong>
<ul>
{env.acceptedValues.map((example, index) => (
<li key={index}>
<code>{example}</code>
</li>
))}
</ul>
</section>
)}
{env.examples && env.examples.length > 0 && (
<section>
<strong>Examples:</strong>
<ul>
{env.examples.map((example, index) => (
<li key={index}>
<code>{example}</code>
</li>
))}
</ul>
</section>
)}
{env.notes && renderAdmonition("note", env.notes)}
{env.important && renderAdmonition("important", env.important)}
{env.warnings && renderAdmonition("warning", env.warnings)}
<div style={{ marginTop: "0.5rem" }}>{env.children}</div>
</div>
<hr />
</>
);
}

View File

@@ -0,0 +1,89 @@
import React from "react";
import EnvVars, { EnvVarProps } from "./EnvVars";
const settings: EnvVarProps[] = [
{
name: "TZ",
description: [
"The time zone to use."
],
type: "text",
defaultValue: "UTC",
required: false,
examples: ["America/New_York", "Europe/London", "Asia/Tokyo"],
},
{
name: "DRY_RUN",
description: [
"When enabled, simulates irreversible operations (like deletions and notifications) without making actual changes."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "LOGGING__LOGLEVEL",
description: [
"Controls the detail level of application logs."
],
type: "text",
defaultValue: "Information",
required: false,
acceptedValues: ["Verbose", "Debug", "Information", "Warning", "Error", "Fatal"],
},
{
name: "LOGGING__FILE__ENABLED",
description: [
"Enables logging to a file."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "LOGGING__FILE__PATH",
description: [
"Directory where log files will be saved."
],
type: "text",
defaultValue: "Empty (file is saved where the app is)",
required: false,
},
{
name: "LOGGING__ENHANCED",
description: [
"Provides more detailed descriptions in logs whenever possible.",
"Will be deprecated in a future version."
],
type: "boolean",
defaultValue: "true",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "HTTP_MAX_RETRIES",
description: [
"The number of times to retry a failed HTTP call.",
"Applies when communicating with *arrs, download clients and other services through HTTP calls."
],
type: "positive integer number",
defaultValue: "0",
required: false,
},
{
name: "HTTP_TIMEOUT",
description: [
"The number of seconds to wait before failing an HTTP call.",
"Applies to calls to *arrs, download clients, and other services."
],
type: "positive integer number",
defaultValue: "100",
required: false,
}
];
export default function GeneralSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,68 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "LIDARR__ENABLED",
description: [
"Enables or disables Lidarr cleanup."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "LIDARR__BLOCK__TYPE",
description: [
"Determines how file blocking works for Lidarr."
],
type: "text",
defaultValue: "blacklist",
required: false,
acceptedValues: ["blacklist", "whitelist"],
},
{
name: "LIDARR__BLOCK__PATH",
description: [
"Path to the blocklist file (local file or URL).",
"The value must be JSON compatible.",
{
type: "code",
title: "The blocklists support the following patterns:",
content: `*example // file name ends with \"example\"
example* // file name starts with \"example\"
*example* // file name has \"example\" in the name
example // file name is exactly the word \"example\"
regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with \"regex:\"`,
}
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["/blocklist.json", "https://example.com/blocklist.json"]
},
{
name: "LIDARR__INSTANCES__0__URL",
description: [
"URL of the Lidarr instance."
],
type: "text",
defaultValue: "http://localhost:8686",
required: false,
examples: ["http://localhost:8686", "http://lidarr:8686"],
},
{
name: "LIDARR__INSTANCES__0__APIKEY",
description: [
"API key for the Lidarr instance."
],
type: "text",
defaultValue: "Empty",
required: false,
}
];
export default function LidarrSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,71 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "RADARR__ENABLED",
description: [
"Enables or disables Radarr cleanup."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "RADARR__BLOCK__TYPE",
description: [
"Determines how file blocking works for Radarr."
],
type: "text",
defaultValue: "blacklist",
required: false,
acceptedValues: ["blacklist", "whitelist"],
},
{
name: "RADARR__BLOCK__PATH",
description: [
"Path to the blocklist file (local file or URL).",
"The value must be JSON compatible.",
{
type: "code",
title: "The blocklists support the following patterns:",
content: `*example // file name ends with \"example\"
example* // file name starts with \"example\"
*example* // file name has \"example\" in the name
example // file name is exactly the word \"example\"
regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with \"regex:\"`,
}
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["/blocklist.json", "https://example.com/blocklist.json"],
notes: [
"[This blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), [this permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive) and [this whitelist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist) can be used for Sonarr and Radarr."
]
},
{
name: "RADARR__INSTANCES__0__URL",
description: [
"URL of the Radarr instance."
],
type: "text",
defaultValue: "http://localhost:7878",
required: false,
examples: ["http://localhost:7878", "http://radarr:7878"],
},
{
name: "RADARR__INSTANCES__0__APIKEY",
description: [
"API key for the Radarr instance."
],
type: "text",
defaultValue: "Empty",
required: false
}
];
export default function RadarrSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,81 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "SONARR__ENABLED",
description: [
"Enables or disables Sonarr cleanup."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "SONARR__BLOCK__TYPE",
description: [
"Determines how file blocking works for Sonarr."
],
type: "text",
defaultValue: "blacklist",
required: false,
acceptedValues: ["blacklist", "whitelist"],
},
{
name: "SONARR__BLOCK__PATH",
description: [
"Path to the blocklist file (local file or URL).",
"The value must be JSON compatible.",
{
type: "code",
title: "The blocklists support the following patterns:",
content: `*example // file name ends with \"example\"
example* // file name starts with \"example\"
*example* // file name has \"example\" in the name
example // file name is exactly the word \"example\"
regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with \"regex:\"`,
}
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["/blocklist.json", "https://example.com/blocklist.json"],
notes: [
"[This blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), [this permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive) and [this whitelist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist) can be used for Sonarr and Radarr."
]
},
{
name: "SONARR__SEARCHTYPE",
description: [
"Determines what to search for after removing a queue item."
],
type: "text",
defaultValue: "Episode",
required: false,
acceptedValues: ["Episode", "Season", "Series"],
},
{
name: "SONARR__INSTANCES__0__URL",
description: [
"URL of the Sonarr instance."
],
type: "text",
defaultValue: "http://localhost:8989",
required: false,
examples: ["http://localhost:8989", "http://sonarr:8989"],
},
{
name: "SONARR__INSTANCES__0__APIKEY",
description: [
"API key for the Sonarr instance."
],
type: "text",
defaultValue: "Empty",
required: false
}
];
export default function SonarrSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,95 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "CONTENTBLOCKER__ENABLED",
description: [
"Enables or disables the Content Blocker functionality.",
"When enabled, processes all items in the *arr queue and marks unwanted files."
],
type: "boolean",
defaultValue: "false",
required: false,
examples: ["true", "false"],
},
{
name: "TRIGGERS__CONTENTBLOCKER",
description: [
"Cron schedule for the Content Blocker job."
],
type: "text",
reference: "https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html",
defaultValue: "0 0/5 * * * ?",
defaultValueComment: "every 5 minutes",
required: "Only required if CONTENTBLOCKER__ENABLED is true",
examples: ["0 0/5 * * * ?", "0 0 * * * ?", "0 0 0/1 * * ?"],
notes: [
"Maximum interval is 6 hours."
]
},
{
name: "CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH",
description: [
"Local path to the file containing downloads to be ignored from being processed by Cleanuperr.",
"If the contents of the file are changed, they will be reloaded on the next job run.",
"This file is not automatically created, so you need to create it manually.",
{
type: "list",
title:
"Accepted values inside the file (each value needs to be on a new line):",
content: [
"torrent hash",
"qBitTorrent tag or category",
"Deluge label",
"Transmission category (last directory from the save location)",
"torrent tracker domain",
],
},
{
type: "code",
title: "Example of file contents:",
content: `fa800a7d7c443a2c3561d1f8f393c089036dade1
tv-sonarr
qbit-tag
mytracker.com
...`,
},
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["/ignored.txt", "/config/ignored.txt"],
warnings: [
"Some people have experienced problems using Docker where the mounted file would not update inside the container if it was modified on the host. This is a Docker configuration problem and can not be solved by cleanuperr.",
],
},
{
name: "CONTENTBLOCKER__IGNORE_PRIVATE",
description: [
"Controls whether to ignore downloads from private trackers from being processed by Cleanuperr."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "CONTENTBLOCKER__DELETE_PRIVATE",
description: [
"Controls whether to delete private downloads that have all files blocked from the download client.",
"Has no effect if CONTENTBLOCKER__IGNORE_PRIVATE is true."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
important: [
"Setting CONTENTBLOCKER__DELETE_PRIVATE=true means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
]
}
];
export default function ContentBlockerGeneralSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,54 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "DOWNLOADCLEANER__CATEGORIES__0__NAME",
description: ["Name of the category to clean."],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["tv-sonarr", "movies-radarr", "music-lidarr"],
notes: [
"The category name must match the category that was set in the *arr.",
"For qBittorrent, the category name is the name of the download category.",
"For Deluge, the category name is the name of the label.",
"For Transmission, the category name is the last directory from the save location.",
],
},
{
name: "DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO",
description: ["Maximum ratio to reach before removing a download."],
type: "decimal number",
defaultValue: "-1",
required: false,
examples: ["-1", "1.0", "2.0", "3.0"],
notes: ["`-1` means no limit/disabled."],
},
{
name: "DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME",
description: [
"Minimum number of hours to seed before removing a download, if the ratio has been met.",
"Used with `MAX_RATIO` to ensure a minimum seed time.",
],
type: "positive decimal number",
defaultValue: "0",
required: false,
examples: ["0", "24", "48", "72"],
},
{
name: "DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME",
description: [
"Maximum number of hours to seed before removing a download.",
],
type: "decimal number",
defaultValue: "-1",
required: false,
examples: ["-1", "24", "48", "72"],
notes: ["`-1` means no limit/disabled."],
},
];
export default function DownloadCleanerCleanupSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,81 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "DOWNLOADCLEANER__ENABLED",
description: [
"Enables or disables the Download Cleaner functionality.",
"When enabled, automatically cleans up downloads that have been seeding for a certain amount of time."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "TRIGGERS__DOWNLOADCLEANER",
description: [
"Cron schedule for the Download Cleaner job."
],
type: "text",
reference: "https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html",
defaultValue: "0 0 * * * ?",
defaultValueComment: "every hour",
required: false,
notes: [
"Maximum interval is 6 hours."
]
},
{
name: "DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH",
description: [
"Local path to the file containing ignored downloads.",
"If the contents of the file are changed, they will be reloaded on the next job run.",
{
type: "list",
title: "Accepted values inside the file (each value needs to be on a new line):",
content: [
"torrent hash",
"qBitTorrent tag or category",
"Deluge label",
"Transmission category (last directory from the save location)",
"torrent tracker domain"
]
},
{
type: "code",
title: "Example of file contents:",
content: `fa800a7d7c443a2c3561d1f8f393c089036dade1
tv-sonarr
qbit-tag
mytracker.com
...`
}
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["/ignored.txt", "/config/ignored.txt"],
warnings: [
"Some people have experienced problems using Docker where the mounted file would not update inside the container if it was modified on the host. This is a Docker configuration problem and can not be solved by cleanuperr."
]
},
{
name: "DOWNLOADCLEANER__DELETE_PRIVATE",
description: [
"Controls whether to delete private downloads."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
important: [
"Setting `DOWNLOADCLEANER__DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
]
}
];
export default function DownloadCleanerGeneralSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,119 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "DOWNLOAD_CLIENT",
description: [
"Specifies which download client is used by *arrs."
],
type: "text",
defaultValue: "none",
required: false,
acceptedValues: ["none", "qbittorrent", "deluge", "transmission", "disabled"],
notes: [
"Only one download client can be enabled at a time. If you have more than one download client, you should deploy multiple instances of Cleanuperr."
],
warnings: [
"When the download client is set to `disabled`, the Queue Cleaner will be able to remove items that are failed to be imported even if there is no download client configured. This means that all downloads, including private ones, will be completely removed.",
"Setting `DOWNLOAD_CLIENT=disabled` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
]
},
{
name: "QBITTORRENT__URL",
description: [
"URL of the qBittorrent instance."
],
type: "text",
defaultValue: "http://localhost:8080",
required: false,
examples: ["http://localhost:8080", "http://192.168.1.100:8080", "https://mydomain.com:8080"],
},
{
name: "QBITTORRENT__URL_BASE",
description: [
"Adds a prefix to the qBittorrent url, such as `[QBITTORRENT__URL]/[QBITTORRENT__URL_BASE]/api`."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "QBITTORRENT__PASSWORD",
description: [
"Password for qBittorrent authentication."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "DELUGE__URL",
description: [
"URL of the Deluge instance."
],
type: "text",
defaultValue: "http://localhost:8112",
required: false,
examples: ["http://localhost:8112", "http://192.168.1.100:8112", "https://mydomain.com:8112"],
},
{
name: "DELUGE__URL_BASE",
description: [
"Adds a prefix to the deluge json url, such as `[DELUGE__URL]/[DELUGE__URL_BASE]/json`."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "DELUGE__PASSWORD",
description: [
"Password for Deluge authentication."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "TRANSMISSION__URL",
description: [
"URL of the Transmission instance."
],
type: "text",
defaultValue: "http://localhost:9091",
required: false,
examples: ["http://localhost:9091", "http://192.168.1.100:9091", "https://mydomain.com:9091"],
},
{
name: "TRANSMISSION__URL_BASE",
description: [
"Adds a prefix to the Transmission rpc url, such as `[TRANSMISSION__URL]/[TRANSMISSION__URL_BASE]/rpc`."
],
type: "text",
defaultValue: "transmission",
required: false,
},
{
name: "TRANSMISSION__USERNAME",
description: [
"Username for Transmission authentication."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "TRANSMISSION__PASSWORD",
description: [
"Password for Transmission authentication."
],
type: "text",
defaultValue: "Empty",
required: false,
}
];
export default function DownloadClientSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,75 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "APPRISE__URL",
description: [
"[Apprise url](https://github.com/caronc/apprise-api) where to send notifications.",
"The `/notify` endpoint is automatically appended."
],
type: "text",
defaultValue: "Empty",
required: false,
examples: [
"http://localhost:8000"
]
},
{
name: "APPRISE__KEY",
description: ["[Apprise configuration key](https://github.com/caronc/apprise-api?tab=readme-ov-file#screenshots) containing all 3rd party notification providers which Cleanuperr would notify."],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "APPRISE__ON_IMPORT_FAILED_STRIKE",
description: [
"Controls whether to notify when an item receives a failed import strike.",
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "APPRISE__ON_STALLED_STRIKE",
description: [
"Controls whether to notify when an item receives a stalled download strike. This includes strikes for being stuck while downloading metadata.",
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "APPRISE__ON_SLOW_STRIKE",
description: [
"Controls whether to notify when an item receives a slow download strike. This includes strikes for having a low download speed or slow estimated finish time.",
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "APPRISE__ON_QUEUE_ITEM_DELETED",
description: ["Controls whether to notify when a queue item is deleted."],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "APPRISE__ON_DOWNLOAD_CLEANED",
description: ["Controls whether to notify when a download is cleaned."],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
];
export default function AppriseSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,78 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "NOTIFIARR__API_KEY",
description: [
"Notifiarr API key for sending notifications.",
"Requires Notifiarr's [Passthrough](https://notifiarr.wiki/pages/integrations/passthrough/) integration to work."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "NOTIFIARR__CHANNEL_ID",
description: [
"Discord channel ID where notifications will be sent."
],
type: "text",
defaultValue: "Empty",
required: false,
},
{
name: "NOTIFIARR__ON_IMPORT_FAILED_STRIKE",
description: [
"Controls whether to notify when an item receives a failed import strike."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "NOTIFIARR__ON_STALLED_STRIKE",
description: [
"Controls whether to notify when an item receives a stalled download strike. This includes strikes for being stuck while downloading metadata."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "NOTIFIARR__ON_SLOW_STRIKE",
description: [
"Controls whether to notify when an item receives a slow download strike. This includes strikes for having a low download speed or slow estimated finish time."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "NOTIFIARR__ON_QUEUE_ITEM_DELETED",
description: [
"Controls whether to notify when a queue item is deleted."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "NOTIFIARR__ON_DOWNLOAD_CLEANED",
description: [
"Controls whether to notify when a download is cleaned."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
}
];
export default function NotifiarrSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,80 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "QUEUECLEANER__ENABLED",
description: [
"Enables or disables the queue cleaning functionality. When enabled, processes all items in the *arr queue."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "TRIGGERS__QUEUECLEANER",
description: [
"Cron schedule for the Queue Cleaner job."
],
type: "text",
reference: "https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html",
defaultValue: "0 0/5 * * * ?",
defaultValueComment: "every 5 minutes",
required: "Only required if QUEUECLEANER__ENABLED is true",
examples: ["0 0/5 * * * ?", "0 0 * * * ?", "0 0 0/1 * * ?"],
notes: [
"Maximum interval is 6 hours.",
"Is ignored if `QUEUECLEANER__RUNSEQUENTIALLY=true` and `CONTENTBLOCKER__ENABLED=true`."
]
},
{
name: "QUEUECLEANER__IGNORED_DOWNLOADS_PATH",
description: [
"Local path to the file containing downloads to be ignored from being processed by Cleanuperr.",
"If the contents of the file are changed, they will be reloaded on the next job run.",
"This file is not automatically created, so you need to create it manually.",
{
type: "list",
title: "Accepted values inside the file (each value needs to be on a new line):",
content: [
"torrent hash",
"qBitTorrent tag or category",
"Deluge label",
"Transmission category (last directory from the save location)",
"torrent tracker domain"
]
},
{
type: "code",
title: "Example of file contents:",
content: `fa800a7d7c443a2c3561d1f8f393c089036dade1
tv-sonarr
qbit-tag
mytracker.com
...`
}
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["/ignored.txt", "/config/ignored.txt"],
warnings: [
"Some people have experienced problems using Docker where the mounted file would not update inside the container if it was modified on the host. This is a Docker configuration problem and can not be solved by cleanuperr."
]
},
{
name: "QUEUECLEANER__RUNSEQUENTIALLY",
description: [
"Controls whether Queue Cleaner runs after Content Blocker instead of in parallel. When true, streamlines the cleaning process by running immediately after Content Blocker."
],
type: "boolean",
defaultValue: "true",
required: false,
acceptedValues: ["true", "false"]
}
];
export default function QueueCleanerGeneralSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,64 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES",
description: [
"Number of strikes before removing a failed import. Set to `0` to never remove failed imports.",
"A strike is given when an item fails to be imported."
],
type: "positive integer number",
defaultValue: "0",
required: false,
examples: ["0", "3", "10"],
notes: [
"If not set to `0`, the minimum value is `3`."
]
},
{
name: "QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE",
description: [
"Controls whether to ignore failed imports from private trackers from being processed by Cleanuperr."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE",
description: [
"Controls whether to delete failed imports from private trackers from the download client.",
"Has no effect if QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE is true."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
important: [
"Setting this to true means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
]
},
{
name: "QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0",
description: [
"Patterns to look for in failed import messages that should be ignored.",
"Multiple patterns can be specified using incrementing numbers starting from 0.",
{
type: "code",
title: "Configuration example:",
content: `QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0=title mismatch
QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1=manual import required`
}
],
type: "text array",
defaultValue: "Empty",
required: false,
examples: ["title mismatch", "manual import required"],
}
];
export default function QueueCleanerImportFailedSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,89 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "QUEUECLEANER__SLOW_MAX_STRIKES",
description: [
"Number of strikes before removing a slow download. Set to `0` to never remove slow downloads.",
"A strike is given when an item is slow."
],
type: "positive integer number",
defaultValue: "0",
required: false,
examples: ["0", "3", "10"],
notes: [
"If not set to 0, the minimum value is 3."
]
},
{
name: "QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS",
description: [
"Controls whether to remove the given strikes if the download speed or estimated time are not slow anymore."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "QUEUECLEANER__SLOW_IGNORE_PRIVATE",
description: [
"Controls whether to ignore slow downloads from private trackers from being processed by Cleanuperr."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "QUEUECLEANER__SLOW_DELETE_PRIVATE",
description: [
"Controls whether slow downloads from private trackers should be removed from the download client.",
"Has no effect if QUEUECLEANER__SLOW_IGNORE_PRIVATE is true."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
important: [
"Setting this to true means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
]
},
{
name: "QUEUECLEANER__SLOW_MIN_SPEED",
description: [
"The minimum speed a download should have.",
"Downloads receive strikes if their speed falls below this value.",
"If not specified, downloads will not receive strikes for slow download speed."
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["1.5KB", "400KB", "2MB"],
},
{
name: "QUEUECLEANER__SLOW_MAX_TIME",
description: [
"The maximum estimated hours a download should take to finish. Downloads receive strikes if their estimated finish time is above this value. If not specified (or 0), downloads will not receive strikes for slow estimated finish time."
],
type: "positive number",
defaultValue: "0",
required: false,
examples: ["0", "1.5", "24", "48"],
},
{
name: "QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE",
description: [
"Downloads above this size will not be removed for being slow."
],
type: "text",
defaultValue: "Empty",
required: false,
examples: ["10KB", "200MB", "3GB"],
}
];
export default function QueueCleanerSlowSettings() {
return <EnvVars vars={settings} />;
}

View File

@@ -0,0 +1,57 @@
import React from "react";
import EnvVars, { EnvVarProps } from "../EnvVars";
const settings: EnvVarProps[] = [
{
name: "QUEUECLEANER__STALLED_MAX_STRIKES",
description: [
"Number of strikes before removing a stalled download. Set to `0` to never remove stalled downloads.",
"A strike is given when an item is stalled (not downloading) or stuck while downloading metadata (qBitTorrent only)."
],
type: "positive integer number",
defaultValue: "0",
required: false,
examples: ["0", "3", "10"],
notes: [
"If not set to 0, the minimum value is 3."
]
},
{
name: "QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS",
description: [
"Controls whether to remove the given strikes if any download progress was made since last checked."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "QUEUECLEANER__STALLED_IGNORE_PRIVATE",
description: [
"Controls whether to ignore stalled downloads from private trackers from being processed by Cleanuperr."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
},
{
name: "QUEUECLEANER__STALLED_DELETE_PRIVATE",
description: [
"Controls whether stalled downloads from private trackers should be removed from the download client.",
"Has no effect if QUEUECLEANER__STALLED_IGNORE_PRIVATE is true."
],
type: "boolean",
defaultValue: "false",
required: false,
acceptedValues: ["true", "false"],
important: [
"Setting this to true means you don't care about seeding, ratio, H&R and potentially losing your private tracker account."
]
}
];
export default function QueueCleanerStalledSettings() {
return <EnvVars vars={settings} />;
}

135
docs/src/css/custom.css Normal file
View File

@@ -0,0 +1,135 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #3e0d60;
--ifm-color-primary-dark: #360b55;
--ifm-color-primary-darker: #2e094a;
--ifm-color-primary-darkest: #23073a;
--ifm-color-primary-light: #4b1573;
--ifm-color-primary-lighter: #561c85;
--ifm-color-primary-lightest: #642a95;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(40, 10, 60, 0.1);
}
[data-theme='dark'] {
--ifm-color-primary: #6b3fa0;
--ifm-color-primary-dark: #5f3891;
--ifm-color-primary-darker: #553283;
--ifm-color-primary-darkest: #44276a;
--ifm-color-primary-light: #7f54b0;
--ifm-color-primary-lighter: #9168bd;
--ifm-color-primary-lightest: #a27bcc;
--docusaurus-highlighted-code-line-bg: rgba(100, 60, 130, 0.2);
}
[data-theme='light'] code {
padding: 0.2em 0.4em;
border-radius: 4px;
font-size: 0.95em;
}
[data-theme='dark'] code {
background-color: rgba(255, 255, 255, 0.08);
color: #f8f8f2;
padding: 0.2em 0.4em;
border-radius: 4px;
font-size: 0.95em;
border: 1px solid rgba(255, 255, 255, 0.15);
}
.admonition ul {
margin-bottom: 0;
}
.env-var-block {
scroll-margin-top: 80px;
padding: 0.5rem;
}
.env-var-block + hr {
margin-top: 16px;
}
.env-var-highlight {
background-color: #fff3cd;
color: #000;
border-radius: 0.5rem;
transition:
background-color 1s ease-in-out,
color 1s ease-in-out,
border-radius 1s ease-in-out,
padding 1s ease-in-out;
}
.env-var-highlight code {
background-color: #fff3cd;
color: #000;
border-radius: 0.25rem;
transition:
background-color 1s ease-in-out,
color 1s ease-in-out,
border-radius 1s ease-in-out,
padding 1s ease-in-out;
}
.env-var-highlight > * {
color: initial;
}
.highlight-removing {
background-color: transparent;
color: #fff;
border-radius: 0;
transition:
background-color color 1s ease-in-out,
color 1s ease-in-out,
border-radius 1s ease-in-out,
padding 1s ease-in-out;
}
.admonition {
margin: 1rem 0;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
border-left: 4px solid;
}
.admonition-heading {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.admonition-heading h5 {
margin: 0;
font-weight: 600;
}
.admonition-content {
margin-bottom: 0.5rem;
}
.admonition-important {
background-color: rgba(220, 53, 69, 0.1);
border-left-color: #dc3545;
}
.admonition-warning {
background-color: rgba(255, 193, 7, 0.1);
border-left-color: #ffc107;
}
.admonition-note {
background-color: rgba(0, 123, 255, 0.1);
border-left-color: #007bff;
}
html {
scroll-behavior: smooth;
}

View File

@@ -0,0 +1,23 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

44
docs/src/pages/index.tsx Normal file
View File

@@ -0,0 +1,44 @@
import type {ReactNode} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import Heading from '@theme/Heading';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<Heading as="h1" className="hero__title">
{siteConfig.title}
</Heading>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/cleanuperr">
Documentation
</Link>
</div>
</div>
</header>
);
}
export default function Home(): ReactNode {
const {siteConfig} = useDocusaurusContext();
return (
<Layout
title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />">
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>
);
}

0
docs/static/.nojekyll vendored Normal file
View File

BIN
docs/static/img/cleanuperr.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

10
docs/static/img/cleanuperr.svg vendored Normal file
View File

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 112 KiB

BIN
docs/static/img/favicon.ico vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

8
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
},
"exclude": [".docusaurus", "build"]
}

8994
docs/yarn.lock Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,772 +0,0 @@
## Table of contents
- [General settings](#general-settings)
- [Queue Cleaner settings](#queue-cleaner-settings)
- [Content Blocker settings](#content-blocker-settings)
- [Download Cleaner settings](#download-cleaner-settings)
- [Download Client settings](#download-client-settings)
- [Arr settings](#arr-settings)
- [Notification settings](#notification-settings)
- [Notifiarr settings](#notifiarr__api_key)
- [Apprise settings](#apprise__url)
- [Advanced settings](#advanced-settings)
#
### General settings
#### **`TZ`**
- The time zone to use.
- Type: String.
- Possible values: Any valid timezone.
- Default: `UTC`.
- Required: No.
#### **`DRY_RUN`**
- When enabled, simulates irreversible operations (like deletions and notifications) without making actual changes.
- Type: Boolean.
- Possible values: `true`, `false`.
- Default: `false`.
- Required: No.
#### **`LOGGING__LOGLEVEL`**
- Controls the detail level of application logs.
- Type: String.
- Possible values: `Verbose`, `Debug`, `Information`, `Warning`, `Error`, `Fatal`.
- Default: `Information`.
- Required: No.
#### **`LOGGING__FILE__ENABLED`**
- Enables logging to a file.
- Type: Boolean.
- Possible values: `true`, `false`.
- Default: `false`.
- Required: No.
#### **`LOGGING__FILE__PATH`**
- Directory where log files will be saved.
- Type: String.
- Default: Empty.
- Required: No.
#### **`LOGGING__ENHANCED`**
- Provides more detailed descriptions in logs whenever possible.
- Type: Boolean.
- Possible values: `true`, `false`.
- Default: `true`.
- Required: No.
</details>
#
### Queue Cleaner settings
#### **`TRIGGERS__QUEUECLEANER`**
- Cron schedule for the queue cleaner job.
- Type: String - [Quartz cron format](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html).
- Default: `0 0/5 * * * ?` (every 5 minutes).
- Required: Yes if queue cleaner is enabled.
> [!NOTE]
> - Maximum interval is 6 hours.
> - Is ignored if `QUEUECLEANER__RUNSEQUENTIALLY=true` and `CONTENTBLOCKER__ENABLED=true`.
#### **`QUEUECLEANER__ENABLED`**
- Enables or disables the queue cleaning functionality.
- When enabled, processes all items in the *arr queue.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `true`
- Required: No.
#### **`QUEUECLEANER__IGNORED_DOWNLOADS_PATH`**
- Local path to the file containing ignored downloads.
- If the contents of the file are changed, they will be reloaded on the next job run.
- Accepted values:
- torrent hash
- qBitTorrent tag or category
- Deluge label
- Transmission category (last directory from the save location)
- torrent tracker domain
- Each value needs to be on a new line.
- Type: String.
- Default: Empty.
- Required: No.
- Example: `/ignored.txt`.
- Example of file contents:
```
fa800a7d7c443a2c3561d1f8f393c089036dade1
tv-sonarr
qbit-tag
mytracker.com
...
```
>[!IMPORTANT]
> Some people have experienced problems using Docker where the mounted file would not update inside the container if it was modified on the host. This is a Docker configuration problem and can not be solved by cleanuperr.
#### **`QUEUECLEANER__RUNSEQUENTIALLY`**
- Controls whether queue cleaner runs after content blocker instead of in parallel.
- When `true`, streamlines the cleaning process by running immediately after content blocker.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `true`
- Required: No.
#### **`QUEUECLEANER__IMPORT_FAILED_MAX_STRIKES`**
- Number of strikes before removing a failed import.
- Set to `0` to never remove failed imports.
- A strike is given when an item fails to be imported.
- Type: Integer
- Default: `0`
- Required: No.
> [!NOTE]
> If not set to `0`, the minimum value is `3`.
#### **`QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE`**
- Controls whether to ignore failed imports from private trackers.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE`**
- Controls whether to delete failed imports from private trackers from the download client.
- Has no effect if `QUEUECLEANER__IMPORT_FAILED_IGNORE_PRIVATE` is `true`.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
> [!WARNING]
> Setting `QUEUECLEANER__IMPORT_FAILED_DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
#### **`QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS`**
- Patterns to look for in failed import messages that should be ignored.
- Multiple patterns can be specified using incrementing numbers starting from 0.
- Type: String array
- Default: Empty.
- Required: No.
- Example:
```yaml
QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__0: "title mismatch"
QUEUECLEANER__IMPORT_FAILED_IGNORE_PATTERNS__1: "manual import required"
```
#### **`QUEUECLEANER__STALLED_MAX_STRIKES`**
- Number of strikes before removing a stalled download.
- Set to `0` to never remove stalled downloads.
- A strike is given when an item is stalled (not downloading) or stuck while downloading metadata.
- Type: Integer
- Default: `0`
- Required: No.
> [!NOTE]
> If not set to `0`, the minimum value is `3`.
#### **`QUEUECLEANER__STALLED_RESET_STRIKES_ON_PROGRESS`**
- Controls whether to remove the given strikes if any download progress was made since last checked.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`QUEUECLEANER__STALLED_IGNORE_PRIVATE`**
- Controls whether to ignore stalled downloads from private trackers.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`QUEUECLEANER__STALLED_DELETE_PRIVATE`**
- Controls whether stalled downloads from private trackers should be removed from the download client.
- Has no effect if `QUEUECLEANER__STALLED_IGNORE_PRIVATE` is `true`.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
> [!WARNING]
> Setting `QUEUECLEANER__STALLED_DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
#### **`QUEUECLEANER__SLOW_MAX_STRIKES`**
- Number of strikes before removing a slow download.
- Set to `0` to never remove slow downloads.
- A strike is given when an item is slow.
- Type: Integer
- Default: `0`
- Required: No.
> [!NOTE]
> If not set to `0`, the minimum value is `3`.
#### **`QUEUECLEANER__SLOW_RESET_STRIKES_ON_PROGRESS`**
- Controls whether to remove the given strikes if the download speed or estimated time are not slow anymore.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`QUEUECLEANER__SLOW_IGNORE_PRIVATE`**
- Controls whether to ignore slow downloads from private trackers.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`QUEUECLEANER__SLOW_DELETE_PRIVATE`**
- Controls whether slow downloads from private trackers should be removed from the download client.
- Has no effect if `QUEUECLEANER__SLOW_IGNORE_PRIVATE` is `true`.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
> [!WARNING]
> Setting `QUEUECLEANER__SLOW_DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
#### **`QUEUECLEANER__SLOW_MIN_SPEED`**
- The minimum speed a download should have.
- Downloads receive strikes if their speed falls bellow this value.
- If not specified, downloads will not receive strikes for slow download speed.
- Type: String.
- Default: Empty.
- Required: No.
- Value examples: `1.5KB`, `400KB`, `2MB`
#### **`QUEUECLEANER__SLOW_MAX_TIME`**
- The maximum estimated hours a download should take to finish.
- Downloads receive strikes if their estimated finish time is above this value.
- If not specified (or `0`), downloads will not receive strikes for slow estimated finish time.
- Type: Integer.
- Default: `0`.
- Required: No.
#### **`QUEUECLEANER__SLOW_IGNORE_ABOVE_SIZE`**
- Downloads above this size will not be removed for being slow.
- Type: String.
- Default: Empty.
- Required: No.
- Value examples: `10KB`, `200MB`, `3GB`.
#
### Content Blocker settings
#### **`TRIGGERS__CONTENTBLOCKER`**
- Cron schedule for the content blocker job.
- Type: String - [Quartz cron format](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html).
- Default: `0 0/5 * * * ?` (every 5 minutes).
- Required: No.
> [!NOTE]
> - Maximum interval is 6 hours.
#### **`CONTENTBLOCKER__ENABLED`**
- Enables or disables the content blocker functionality.
- When enabled, processes all items in the *arr queue and marks unwanted files.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`CONTENTBLOCKER__IGNORED_DOWNLOADS_PATH`**
- Local path to the file containing ignored downloads.
- If the contents of the file are changed, they will be reloaded on the next job run.
- Accepted values:
- torrent hash
- qBitTorrent tag or category
- Deluge label
- Transmission category (last directory from the save location)
- torrent tracker domain
- Each value needs to be on a new line.
- Type: String.
- Default: Empty.
- Required: No.
- Example: `/ignored.txt`.
- Example of file contents:
```
fa800a7d7c443a2c3561d1f8f393c089036dade1
tv-sonarr
qbit-tag
mytracker.com
...
```
>[!IMPORTANT]
> Some people have experienced problems using Docker where the mounted file would not update inside the container if it was modified on the host. This is a Docker configuration problem and can not be solved by cleanuperr.
#### **`CONTENTBLOCKER__IGNORE_PRIVATE`**
- Controls whether to ignore downloads from private trackers.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`CONTENTBLOCKER__DELETE_PRIVATE`**
- Controls whether to delete private downloads that have all files blocked from the download client.
- Has no effect if `CONTENTBLOCKER__IGNORE_PRIVATE` is `true`.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
> [!WARNING]
> Setting `CONTENTBLOCKER__DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
#
### Download Cleaner settings
#### **`TRIGGERS__DOWNLOADCLEANER`**
- Cron schedule for the download cleaner job.
- Type: String - [Quartz cron format](https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html).
- Default: `0 0 * * * ?` (every hour).
- Required: No.
> [!NOTE]
> - Maximum interval is 6 hours.
#### **`DOWNLOADCLEANER__ENABLED`**
- Enables or disables the download cleaner functionality.
- When enabled, automatically cleans up downloads that have been seeding for a certain amount of time.
- Type: Boolean.
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`DOWNLOADCLEANER__IGNORED_DOWNLOADS_PATH`**
- Local path to the file containing ignored downloads.
- If the contents of the file are changed, they will be reloaded on the next job run.
- Accepted values:
- torrent hash
- qBitTorrent tag or category
- Deluge label
- Transmission category (last directory from the save location)
- torrent tracker domain
- Each value needs to be on a new line.
- Type: String.
- Default: Empty.
- Required: No.
- Example: `/ignored.txt`.
- Example of file contents:
```
fa800a7d7c443a2c3561d1f8f393c089036dade1
tv-sonarr
qbit-tag
mytracker.com
...
```
>[!IMPORTANT]
> Some people have experienced problems using Docker where the mounted file would not update inside the container if it was modified on the host. This is a Docker configuration problem and can not be solved by cleanuperr.
#### **`DOWNLOADCLEANER__DELETE_PRIVATE`**
- Controls whether to delete private downloads.
- Type: Boolean.
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
> [!WARNING]
> Setting `DOWNLOADCLEANER__DELETE_PRIVATE=true` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
#### **`DOWNLOADCLEANER__CATEGORIES__0__NAME`**
- Name of the category to clean.
- Type: String.
- Default: Empty.
- Required: No.
> [!NOTE]
> The category name must match the category that was set in the *arr.
>
> For qBittorrent, the category name is the name of the download category.
>
> For Deluge, the category name is the name of the label.
>
> For Transmission, the category name is the last directory from the save location.
#### **`DOWNLOADCLEANER__CATEGORIES__0__MAX_RATIO`**
- Maximum ratio to reach before removing a download.
- Type: Decimal.
- Possible values: `-1` or greater (`-1` means no limit or disabled).
- Default: `-1`
- Required: No.
#### **`DOWNLOADCLEANER__CATEGORIES__0__MIN_SEED_TIME`**
- Minimum number of hours to seed before removing a download, if the ratio has been met.
- Used with `MAX_RATIO` to ensure a minimum seed time.
- Type: Decimal.
- Possible values: `0` or greater.
- Default: `0`
- Required: No.
#### **`DOWNLOADCLEANER__CATEGORIES__0__MAX_SEED_TIME`**
- Maximum number of hours to seed before removing a download.
- Type: Decimal.
- Possible values: `-1` or greater (`-1` means no limit or disabled).
- Default: `-1`
- Required: No.
> [!NOTE]
> A download is cleaned when any of (`MAX_RATIO` & `MIN_SEED_TIME`) or `MAX_SEED_TIME` is reached.
> [!NOTE]
> Multiple categories can be specified using this format, where `<NUMBER>` starts from 0:
> ```yaml
> DOWNLOADCLEANER__CATEGORIES__<NUMBER>__NAME
> DOWNLOADCLEANER__CATEGORIES__<NUMBER>__MAX_RATIO
> DOWNLOADCLEANER__CATEGORIES__<NUMBER>__MIN_SEED_TIME
> DOWNLOADCLEANER__CATEGORIES__<NUMBER>__MAX_SEED_TIME
> ```
#
### Download Client settings
#### **`DOWNLOAD_CLIENT`**
- Specifies which download client is used by *arrs.
- Type: String.
- Possible values: `none`, `qbittorrent`, `deluge`, `transmission`, `disabled`.
- Default: `none`
- Required: No.
> [!NOTE]
> Only one download client can be enabled at a time. If you have more than one download client, you should deploy multiple instances of cleanuperr.
> [!IMPORTANT]
> When the download client is set to `disabled`, the queue cleaner will be able to remove items that are failed to be imported even if there is no download client configured. This means that all downloads, including private ones, will be completely removed.
>
> Setting `DOWNLOAD_CLIENT=disabled` means you don't care about seeding, ratio, H&R and potentially losing your private tracker account.
#### **`QBITTORRENT__URL`**
- URL of the qBittorrent instance.
- Type: String.
- Default: `http://localhost:8080`.
- Required: No.
#### **`QBITTORRENT__URL_BASE`**
- Adds a prefix to the qBittorrent url, such as `[QBITTORRENT__URL]/[QBITTORRENT__URL_BASE]/api`.
- Type: String.
- Default: Empty.
- Required: No.
#### **`QBITTORRENT__USERNAME`**
- Username for qBittorrent authentication.
- Type: String.
- Default: Empty.
- Required: No.
#### **`QBITTORRENT__PASSWORD`**
- Password for qBittorrent authentication.
- Type: String.
- Default: Empty.
- Required: No.
#### **`DELUGE__URL`**
- URL of the Deluge instance.
- Type: String.
- Default: `http://localhost:8112`.
- Required: No.
#### **`DELUGE__URL_BASE`**
- Adds a prefix to the deluge json url, such as `[DELUGE__URL]/[DELUGE__URL_BASE]/json`.
- Type: String.
- Default: Empty.
- Required: No.
#### **`DELUGE__PASSWORD`**
- Password for Deluge authentication.
- Type: String.
- Default: Empty.
- Required: No.
#### **`TRANSMISSION__URL`**
- URL of the Transmission instance.
- Type: String.
- Default: `http://localhost:9091`.
- Required: No.
#### **`TRANSMISSION__URL_BASE`**
- Adds a prefix to the Transmission rpc url, such as `[TRANSMISSION__URL]/[TRANSMISSION__URL_BASE]/rpc`.
- Type: String.
- Default: `transmission`.
- Required: No.
#### **`TRANSMISSION__USERNAME`**
- Username for Transmission authentication.
- Type: String.
- Default: Empty.
- Required: No.
#### **`TRANSMISSION__PASSWORD`**
- Password for Transmission authentication.
- Type: String.
- Default: Empty.
- Required: No.
#
### Arr settings
> [!NOTE]
> Multiple instances can be specified for each *arr using this format, where `<NUMBER>` starts from 0:
> ```yaml
> <ARR>__INSTANCES__<NUMBER>__URL
> <ARR>__INSTANCES__<NUMBER>__APIKEY
> ```
#### **`SONARR__ENABLED`**
- Enables or disables Sonarr cleanup.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`SONARR__BLOCK__TYPE`**
- Determines how file blocking works for Sonarr.
- Type: String
- Possible values: `blacklist`, `whitelist`
- Default: `blacklist`
- Required: No.
#### **`SONARR__BLOCK__PATH`**
- Path to the blocklist file (local file or URL).
- Must be JSON compatible.
- Type: String
- Default: Empty.
- Required: No.
> [!NOTE]
> The blocklists support the following patterns:
> ```
> *example // file name ends with "example"
> example* // file name starts with "example"
> *example* // file name has "example" in the name
> example // file name is exactly the word "example"
> regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with "regex:"
> ```
> [!NOTE]
> [This blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), [this permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive) and [this whitelist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist) can be used for Sonarr and Radarr.
#### **`SONARR__SEARCHTYPE`**
- Determines what to search for after removing a queue item.
- Type: String
- Possible values: `Episode`, `Season`, `Series`
- Default: `Episode`
- Required: No.
#### **`SONARR__INSTANCES__0__URL`**
- URL of the Sonarr instance.
- Type: String
- Default: `http://localhost:8989`
- Required: No.
#### **`SONARR__INSTANCES__0__APIKEY`**
- API key for the Sonarr instance.
- Type: String
- Default: Empty.
- Required: No.
#### **`RADARR__ENABLED`**
- Enables or disables Radarr cleanup.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`RADARR__BLOCK__TYPE`**
- Determines how file blocking works for Radarr.
- Type: String
- Possible values: `blacklist`, `whitelist`
- Default: `blacklist`
- Required: No.
#### **`RADARR__BLOCK__PATH`**
- Path to the blocklist file (local file or URL).
- Must be JSON compatible.
- Type: String
- Default: Empty.
- Required: No.
> [!NOTE]
> The blocklists support the following patterns:
> ```
> *example // file name ends with "example"
> example* // file name starts with "example"
> *example* // file name has "example" in the name
> example // file name is exactly the word "example"
> regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with "regex:"
> ```
> [!NOTE]
> [This blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist), [this permissive blacklist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/blacklist_permissive) and [this whitelist](https://raw.githubusercontent.com/flmorg/cleanuperr/refs/heads/main/whitelist) can be used for Sonarr and Radarr.
#### **`RADARR__INSTANCES__0__URL`**
- URL of the Radarr instance.
- Type: String
- Default: `http://localhost:7878`
- Required: No.
#### **`RADARR__INSTANCES__0__APIKEY`**
- API key for the Radarr instance.
- Type: String
- Default: Empty.
- Required: No.
#### **`LIDARR__ENABLED`**
- Enables or disables Lidarr cleanup.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`LIDARR__BLOCK__TYPE`**
- Determines how file blocking works for Lidarr.
- Type: String
- Possible values: `blacklist`, `whitelist`
- Default: `blacklist`
- Required: No.
#### **`LIDARR__BLOCK__PATH`**
- Path to the blocklist file (local file or URL).
- Must be JSON compatible.
- Type: String
- Default: Empty.
- Required: No.
> [!NOTE]
> The blocklists support the following patterns:
> ```
> *example // file name ends with "example"
> example* // file name starts with "example"
> *example* // file name has "example" in the name
> example // file name is exactly the word "example"
> regex:<ANY_REGEX> // regex that needs to be marked at the start of the line with "regex:"
> ```
#### **`LIDARR__INSTANCES__0__URL`**
- URL of the Lidarr instance.
- Type: String
- Default: `http://localhost:8686`
- Required: No.
#### **`LIDARR__INSTANCES__0__APIKEY`**
- API key for the Lidarr instance.
- Type: String
- Default: Empty.
- Required: No.
#
### Notification settings
#### **`NOTIFIARR__API_KEY`**
- Notifiarr API key for sending notifications.
- Requires Notifiarr's [`Passthrough`](https://notifiarr.wiki/en/Website/Integrations/Passthrough) integration to work.
- Type: String
- Default: Empty.
- Required: No.
#### **`NOTIFIARR__CHANNEL_ID`**
- Discord channel ID where notifications will be sent.
- Type: String
- Default: Empty.
- Required: No.
#### **`NOTIFIARR__ON_IMPORT_FAILED_STRIKE`**
- Controls whether to notify when an item receives a failed import strike.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`NOTIFIARR__ON_STALLED_STRIKE`**
- Controls whether to notify when an item receives a stalled download strike. This includes strikes for being stuck while downloading metadata.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`NOTIFIARR__ON_SLOW_STRIKE`**
- Controls whether to notify when an item receives a slow download strike. This includes strikes for having a low download speed or slow estimated finish time.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`NOTIFIARR__ON_QUEUE_ITEM_DELETED`**
- Controls whether to notify when a queue item is deleted.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`NOTIFIARR__ON_DOWNLOAD_CLEANED`**
- Controls whether to notify when a download is cleaned.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`APPRISE__URL`**
- [Apprise url](https://github.com/caronc/apprise-api) where to send notifications.
- Type: String
- Default: Empty.
- Required: No.
#### **`APPRISE__KEY`**
- [Apprise configuration key](https://github.com/caronc/apprise-api?tab=readme-ov-file#screenshots) containing all 3rd party notification providers which Cleanuperr would notify.
- Type: String
- Default: Empty.
- Required: No.
#### **`APPRISE__ON_IMPORT_FAILED_STRIKE`**
- Controls whether to notify when an item receives a failed import strike.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`APPRISE__ON_STALLED_STRIKE`**
- Controls whether to notify when an item receives a stalled download strike. This includes strikes for being stuck while downloading metadata.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`APPRISE__ON_SLOW_STRIKE`**
- Controls whether to notify when an item receives a slow download strike. This includes strikes for having a low download speed or slow estimated finish time.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`APPRISE__ON_QUEUE_ITEM_DELETED`**
- Controls whether to notify when a queue item is deleted.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#### **`APPRISE__ON_DOWNLOAD_CLEANED`**
- Controls whether to notify when a download is cleaned.
- Type: Boolean
- Possible values: `true`, `false`
- Default: `false`
- Required: No.
#
### Advanced settings
#### **`HTTP_MAX_RETRIES`**
- The number of times to retry a failed HTTP call.
- Applies to calls to *arrs, download clients, and other services.
- Type: Integer
- Possible values: `0` or greater
- Default: `0`
- Required: No.
#### **`HTTP_TIMEOUT`**
- The number of seconds to wait before failing an HTTP call.
- Applies to calls to *arrs, download clients, and other services.
- Type: Integer
- Possible values: Greater than `0`.
- Default: `100`
- Required: No.