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
# - PARTIAL_CLEANUP_ENABLED=true
# - STUCK_DOWNLOAD_MINUTES=10.0
# - MAX_DOWNLOAD_HOURS=4.0
# - DAEMON_INTERVAL_SECONDS=60
# - SEARCH_INTERVAL_MINUTES=15
# - IMPORT_INTERVAL_MINUTES=30
# - CLEAN_INTERVAL_MINUTES=240
# - COOLDOWN_REQUEST_MINUTES=15
# - 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). Used for metadata, movie titles, and dynamic TVDB -> TMDB resolution when Sonarr series lack a TMDB ID |
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) |
PARTIAL_CLEANUP_ENABLED |
optional | Enable partial series cleanup for fully aired seasons (default: true) |
STUCK_DOWNLOAD_MINUTES |
optional | Minutes to wait before removing a download with <= 5% progress (default: 10.0) |
MAX_DOWNLOAD_HOURS |
optional | Maximum hours before a download is removed regardless of progress (default: 4.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) |
COOLDOWN_REQUEST_MINUTES |
optional | Post-search cooldown / grace period in minutes before allowing a new search (default: 15) |
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.
- Global Lock & Search Cooldown: Checks if Sonarr or Radarr is actively performing a search (
SeasonSearch,EpisodeSearch,MissingEpisodeSearch,SeriesSearch,MoviesSearch,MissingMoviesSearch) or is within the post-search cooldown grace period (COOLDOWN_REQUEST_MINUTES, default:15min). Aborts the run if busy to avoid concurrent queries or indexer flood. - Automated Queue Stuck Download Cleanup & Blocklisting: Inspects Radarr and Sonarr queues before searching:
- 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). Active in-progress downloads (progress < 0.99) are strictly preserved. - 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 without waiting for the 4-hour cleaner cycle.
- For downloads lingering in the queue whose media is already imported in the library (
- Looks for missing Radarr candidates β triggers
MoviesSearch - Smart Pack vs. Episode Decision:
- Fully Aired / Completed Seasons: Prioritizes a global Pack Search (
SeasonSearch), even if some files already exist on disk. This ensures tracker compatibility when indexers only distribute complete season packs. - Currently Airing Seasons: Uses Individual Search (
EpisodeSearch) episode by episode to grab new releases as they air weekly. - Fallback: If a Pack Search previously failed, falls back to targeted individual episode searches.
- Fully Aired / Completed Seasons: Prioritizes a global Pack Search (
- Standard Search Triggers: Triggers standard
SeasonSearchorEpisodeSearchcommands on Sonarr - Per-cycle quota (12h) to avoid flooding indexers
Tip
Sonarr Quality Profile Recommendation (Upgrades Allowed):
Make sure your Quality Profiles in Sonarr have Upgrades Allowed enabled (with an appropriate cutoff like WEB 1080p). If upgrades are disabled and Sonarr already holds 1 or 2 partial episodes with matching quality, Sonarr will reject incoming season packs ("Existing file meets cutoff"). Enabling upgrades allows Sonarr to replace existing partial files with complete season packs automatically.
- Fetches all missing media from Radarr/Sonarr
- TVDB to TMDB Resolution: If a Sonarr series reports
tmdbId: 0(e.g. series indexed by TVDB ID only), resolves the genuine TMDB ID via TMDB's/findAPI before querying Jellyseerr, preventing lookup failures. - Queue-Aware Safeguard: Actively downloading or queued items in Sonarr/Radarr are protected and will never be marked as missing or queued for deletion.
- Ignores recent releases (
RELEASE_BUFFER_DAYS) and upcoming announced air dates. - 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) - Partial Series Cleanup: Detects series with partially missing episodes in fully aired seasons (
PARTIAL_CLEANUP_ENABLED) β ideal when external scripts delete watched episodes one by one. Removes remaining incomplete season files from disk (remove_seasons) and unmonitors them while preserving other complete seasons and future announced seasons.
- 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
