Automation suite for managing Seerr, Radarr, and Sonarr. I was struggling to keep my library clean so i made this script. The goal of SeerrSentinel is to automate media discovery, library cleanup, and file imports for Seerr, Radarr, and Sonarr.
Important
Any help will be very welcome! It works with my use case but may need some tweaks for yours. Report any bug or feature request 😊.
| File | Role |
|---|---|
seerr_sentinel.py |
Main entry point — orchestrator + config validation |
sentinel_search.py |
Automated searches for missing media |
sentinel_cleaner.py |
Detection and deletion of stalled/missing media |
sentinel_import.py |
Video file injection from the downloads folder |
It is highly recommended to use Docker or Docker Compose. A continuously updated image is available.
- Create a
docker-compose.ymlfile:
services:
seerr-sentinel:
image: ghcr.io/banawoke/seerrsentinel:latest
container_name: seerr-sentinel
restart: unless-stopped
user: "1000:1000" # Run as your local user (recommend)
environment:
- JELLYSEER_API_KEY=your_api_key
- JELLYSEER_URL=http://your-Seerr:5055
- TMDB_API_KEY=your_api_key
- RADARR_API_KEY=your_api_key
- RADARR_URL=http://your-radarr:7878
- SONARR_API_KEY=your_api_key
- SONARR_URL=http://your-sonarr:8989
- DOWNLOADS_PATH=/downloads
# - RELEASE_BUFFER_DAYS=7
# - DELETION_DELAY_DAYS=2
# - KEEP_REQUESTS_OLDER_THAN_DAYS=14
# - DELETE_NOT_MANAGED_JELLYSEERR=true
# - STUCK_DOWNLOAD_MINUTES=20.0
# - MAX_DOWNLOAD_HOURS=6.0
# - DAEMON_INTERVAL_SECONDS=60
# - SEARCH_INTERVAL_MINUTES=15
# - IMPORT_INTERVAL_MINUTES=30
# - CLEAN_INTERVAL_MINUTES=240
# - MOVIE_CYCLE_HOURS=12
# - MOVIE_MAX_SEARCHES=2
# - SEASON_CYCLE_HOURS=12
# - SEASON_MAX_SEARCHES=2
# - EPISODE_CYCLE_HOURS=12
# - EPISODE_MAX_SEARCHES=1
volumes:
- /path/to/your/downloads:/downloads- Start the container:
docker compose up -ddocker run -d \
--name seerr-sentinel \
--restart unless-stopped \
-e JELLYSEER_API_KEY=your_api_key \
-e JELLYSEER_URL=http://your-Seerr:5055 \
-e TMDB_API_KEY=your_api_key \
-e RADARR_API_KEY=your_api_key \
-e RADARR_URL=http://your-radarr:7878 \
-e SONARR_API_KEY=your_api_key \
-e SONARR_URL=http://your-sonarr:8989 \
-e DOWNLOADS_PATH=/downloads \
--user 1000:1000 \
# Other optional variables
-v /path/to/your/downloads:/downloads \
ghcr.io/banawoke/seerrsentinel:latestIf you prefer to run the scripts manually:
- Configure the environment
cp .env.example .env
# Edit .env with your API keys and URLs- Python dependencies
pip install -r requirements.txt- Check your configuration
python3 seerr_sentinel.py --health-checkWhen using the Docker image, the script automatically runs in daemon mode. It stays alive in the background and handles its own schedule.
These intervals are customizable via environment variables (defaults shown below):
- Search: every 15 minutes (
SEARCH_INTERVAL_MINUTES) - Import: every 30 minutes (
IMPORT_INTERVAL_MINUTES) - Clean: every 4 hours (
CLEAN_INTERVAL_MINUTES)
The daemon checks the timers every 60 seconds (DAEMON_INTERVAL_SECONDS).
You can check everything it does in real-time by reading the logs:
docker logs -f seerr-sentinelAlternatively, you can manually trigger operations inside the container:
docker exec -it seerr-sentinel python3 seerr_sentinel.py clean --dry-run
docker exec -it seerr-sentinel python3 seerr_sentinel.py searchIf you are running the scripts manually:
# Check your environment setup before anything else
python3 seerr_sentinel.py --health-check
# Run everything in one go (dry-run to test safely first)
python3 seerr_sentinel.py all --dry-run
# Run everything in one go
python3 seerr_sentinel.py all# Trigger Radarr/Sonarr searches for missing media
python3 seerr_sentinel.py search
# Cleanup (dry-run to test safely first)
python3 seerr_sentinel.py clean --dry-run
python3 seerr_sentinel.py clean
# Video file injection
python3 seerr_sentinel.py import
python3 seerr_sentinel.py import --sonarr --force-id 42| Variable | Required | Description |
|---|---|---|
JELLYSEER_API_KEY |
yes | Seerr API key |
JELLYSEER_URL |
yes | Seerr URL (http://your-Seerr:5055) |
TMDB_API_KEY |
yes | TMDB API key (register here TMDB) |
RADARR_API_KEY |
yes | Radarr API key |
RADARR_URL |
yes | Radarr URL (http://your-radarr:7878) |
SONARR_API_KEY |
yes | Sonarr API key |
SONARR_URL |
yes | Sonarr URL (http://your-sonarr:8989) |
DOWNLOADS_PATH |
yes | Path to the downloads folder |
RELEASE_BUFFER_DAYS |
optional | Days after release before cleanup (default: 7) |
DELETION_DELAY_DAYS |
optional | Grace period before deletion (default: 2) |
KEEP_REQUESTS_OLDER_THAN_DAYS |
optional | Keep Seerr requests older than N days (default: 14) |
DELETE_NOT_MANAGED_JELLYSEERR |
optional | Allow cleaner to delete media not tracked by Jellyseerr (default: true) |
STUCK_DOWNLOAD_MINUTES |
optional | Minutes to wait before removing a download with <= 5% progress (default: 20.0) |
MAX_DOWNLOAD_HOURS |
optional | Maximum hours before a download is removed regardless of progress (default: 6.0) |
DAEMON_INTERVAL_SECONDS |
optional | How often the background daemon checks the timers (default: 60) |
SEARCH_INTERVAL_MINUTES |
optional | How frequently the search module runs (default: 15) |
IMPORT_INTERVAL_MINUTES |
optional | How frequently the import module runs (default: 30) |
CLEAN_INTERVAL_MINUTES |
optional | How frequently the clean module runs (default: 240) |
MOVIE_CYCLE_HOURS |
optional | Duration of search cycle for movies (default: 12) |
MOVIE_MAX_SEARCHES |
optional | Max searches per cycle for a movie (default: 2) |
SEASON_CYCLE_HOURS |
optional | Duration of search cycle for seasons (default: 12) |
SEASON_MAX_SEARCHES |
optional | Max searches per cycle for a season (default: 2) |
EPISODE_CYCLE_HOURS |
optional | Duration of search cycle for individual episodes (default: 12) |
EPISODE_MAX_SEARCHES |
optional | Max searches per cycle for an episode (default: 1) |
When running the all command (or the daemon mode), the script manages its own sub-intervals via a lightweight local JSON cache. By default these are the timers (which can be overriden via .env variables):
- Search: Executes only every 15 min.
- Import: Executes only if 30 minutes have passed since the last run.
- Clean: Executes only if 4 hours have passed since the last run.
- Checks for active commands (global lock)
- Looks for missing Radarr candidates → triggers
MoviesSearch - Looks at Sonarr missing episodes → determines Pack Search vs. Individual Search
- Standard Search & Targeted Force Grab Escalation: Uses standard search commands by default. For targeted blocked Sonarr season scenarios (partially downloaded seasons with cutoff conflicts, fallback pack searches, or previous search failures), it automatically escalates to a Force Grab via Release API (
POST /api/v3/release) to bypass automatic rejection rules (Existing file meets cutofforUnknown Series) - Blocklist-Aware Selection: Excludes blacklisted and previously failed releases, allowing the engine to automatically step over incomplete releases to find valid packs
- Per-cycle quota (12h) to avoid flooding indexers
- Fetches all missing media from Radarr/Sonarr
- Ignores recent releases (
RELEASE_BUFFER_DAYS) - After
DELETION_DELAY_DAYSdays → deletes from Radarr/Sonarr - Declines the Jellyseerr request and sends a notification to the requester (via configured notification agents)
- The declined request stays visible in Jellyseerr — the user can re-request with one click
- Keeps Seerr requests older than
KEEP_REQUESTS_OLDER_THAN_DAYSdays (no decline sent for those) - Smart Queue Cleanup: For downloads lingering in the queue whose media is already imported in the library (
hasFile=True), clears the stale queue entry without blocklisting (blocklist=false) to keep Sonarr/Radarr in sync - Automated Queue Blocklisting: For truly stuck downloads (<= 5% progress after
STUCK_DOWNLOAD_MINUTESor >MAX_DOWNLOAD_HOURS) or unimportable/corrupt releases with missing files, automatically removes & blocklists them (removeFromClient=true,blocklist=true) so indexers can try other releases
- Scans the
DOWNLOADS_PATHfolder - Matches files against missing media using title tokens + TMDB aliases
- Smart Hardlink Detection: Differentiates between hardlinks within the
downloadsdirectory (legitimate multi-source torrents) and external library hardlinks, eliminating false-positive skips - Skips import if content is actively downloading
- Creates hard-links in Radarr/Sonarr media folders
- Triggers
RescanMovie/RescanSeries, waits for confirmation, and re-fetches library state for fresh verification
