A modular Go service that replicates and enhances the functionality of cleaning up missing file references in *arr applications (Sonarr, Radarr, etc.).
Refresharr addresses a common issue where *arr applications maintain database records of media files that no longer exist on disk. This can happen due to:
- Manual file deletion
- Storage failures
- File moves/reorganization
- Network storage disconnections
The service automatically:
- π Scans all media items that claim to have files
- π Verifies if files actually exist on the filesystem
- ποΈ Removes database records for missing files
- π Triggers a refresh to update the application status
- β Sonarr Support: Full API integration with Sonarr v3 for TV shows
- β Radarr Support: Full API integration with Radarr v3 for movies
- β Multi-Service: Run cleanup for both services simultaneously or individually
- β Dry Run Mode: Preview changes before applying them
- β Detailed Logging: Comprehensive progress reporting and statistics
- β Configurable: Environment variables and command-line options
- β Safe Operations: Validates connections and handles errors gracefully
- β Rate Limiting: Configurable delays to avoid API overload
- β Selective Processing: Process specific series or movies by ID
- β Missing Files Report: Generate detailed JSON and terminal reports of missing files
- β Broken Symlink Detection: Scan Radarr root directories for broken symlinks and automatically add missing movies to collection
- β Import Fixer: Automatically resolve stuck Sonarr import issues (already imported episodes)
- π Web UI: Browser-based interface for easier management
- π Scheduling: Automated cleanup runs via cron-like scheduling
- π Notifications: Discord/Slack/Email notifications for cleanup results
The service is designed with modularity in mind to support multiple *arr applications:
Refresharr/
βββ cmd/refresharr/ # CLI application entry point
βββ internal/
β βββ arr/ # Core interfaces and implementations
β β βββ interfaces.go # Service contracts
β β βββ sonarr.go # Sonarr API client
β β βββ cleanup.go # Cleanup orchestration
β β βββ logger.go # Logging implementation
β β βββ progress.go # Progress reporting
β βββ config/ # Configuration management
β βββ filesystem/ # File system operations
βββ pkg/models/ # Shared data models
βββ main.go # Simple entry point
Client: API client interface (Sonarr, future Radarr)CleanupService: Orchestrates the cleanup processFileChecker: Handles filesystem operationsLogger: Structured logging interfaceProgressReporter: User feedback and statistics
This design makes it easy to add support for Radarr by implementing the Client interface with Radarr-specific API calls.
-
Clone the repository:
git clone https://github.com/hnipps/refresharr cd refresharr -
Build the application:
make build
| Variable | Default | Description |
|---|---|---|
SONARR_URL |
http://127.0.0.1:8989 |
Sonarr base URL (auto-set if API key provided) |
SONARR_API_KEY |
(optional) | Sonarr API key |
RADARR_URL |
http://127.0.0.1:7878 |
Radarr base URL (auto-set if API key provided) |
RADARR_API_KEY |
(optional) | Radarr API key |
REQUEST_TIMEOUT |
30s |
HTTP request timeout |
REQUEST_DELAY |
500ms |
Delay between API requests |
CONCURRENT_LIMIT |
5 |
Max concurrent operations |
LOG_LEVEL |
INFO |
Log level (DEBUG, INFO, WARN, ERROR) |
DRY_RUN |
false |
Enable dry run mode |
ADD_MISSING_MOVIES |
false |
Add movies/series to collection when found from broken symlinks |
QUALITY_PROFILE_ID |
12 |
Quality profile ID to use when adding new movies |
Note: At least one service (Sonarr or Radarr) must be configured with both URL and API key.
Sonarr API Key:
- Open Sonarr web interface
- Go to Settings β General
- Copy the API Key value
- Set it as
SONARR_API_KEYenvironment variable
Radarr API Key:
- Open Radarr web interface
- Go to Settings β General
- Copy the API Key value
- Set it as
RADARR_API_KEYenvironment variable
RefreshArr can automatically detect broken symlinks in your Radarr and Sonarr root directories and optionally add missing movies/series to your collection. Broken symlink detection always runs and reports findings, while adding media to your collection is controlled by the ADD_MISSING_MOVIES setting.
- Scan Root Directories: RefreshArr fetches all configured root directories from Radarr
- Find Broken Symlinks: Recursively scans for broken symlinks with movie file extensions (.mkv, .mp4, .avi, etc.)
- Extract TMDB ID: Parses the TMDB ID from directory/filename (e.g.,
Movie Title (2023) [tmdb-12345]) - Check Collection: Verifies if the movie already exists in your Radarr collection
- Add Missing Movies: If not in collection, adds the movie with monitoring enabled and your specified quality profile
- Report Results: Includes these movies in the missing files report with an indication they were added
- Movie directories must include TMDB ID in the format:
Movie Title (Year) [tmdb-12345] - Quality profile must exist in Radarr (default ID: 12, configurable via
QUALITY_PROFILE_ID) - Set
ADD_MISSING_MOVIES=trueto add missing movies to collection (detection always runs)
# Set your API keys (at least one required)
export SONARR_API_KEY="your-sonarr-api-key-here"
export RADARR_API_KEY="your-radarr-api-key-here"
# Run cleanup for all configured services (default)
./refresharr
# Fix stuck Sonarr imports (already imported issues)
./refresharr fix-imports
# Run cleanup for specific service only
./refresharr --service sonarr
./refresharr --service radarr# Dry run (no changes made)
./refresharr --dry-run
# Disable terminal report output (report still saved to file)
./refresharr --no-report
# Run for both services
./refresharr --service both
# Custom instances
./refresharr --sonarr-url "http://192.168.1.100:8989" --sonarr-api-key "your-sonarr-key"
./refresharr --radarr-url "http://192.168.1.100:7878" --radarr-api-key "your-radarr-key"
# Debug logging
./refresharr --log-level DEBUG
# Process specific series or movies only
./refresharr --service sonarr --series-ids "123,456,789"
./refresharr --service radarr --movie-ids "123,456,789"
# Show help
./refresharr --help
# Show version
./refresharr --version
# Fix stuck Sonarr imports (dry run)
./refresharr fix-imports --dry-run
# Fix stuck Sonarr imports (actually remove them)
./refresharr fix-importsThe fix-imports command addresses a common Sonarr issue where downloads get stuck in the queue with "already imported" or similar import errors. This typically happens when:
- Episodes are manually imported outside of Sonarr
- Files are moved or reorganized after download
- Import processes are interrupted
- Database inconsistencies occur
The command identifies stuck import items and attempts to import them before removing from the queue, ensuring no content is lost.
Usage:
# Preview stuck imports (recommended first step)
./refresharr fix-imports --dry-run
# Actually fix the stuck imports
./refresharr fix-imports
# Fix imports with custom Sonarr configuration
./refresharr fix-imports --sonarr-url "http://custom:8989" --sonarr-api-key "your-key"What it does:
- π Scans the Sonarr download queue for stuck items
- π Identifies items with import issues (status = "completed" but not imported)
- π― Attempts to import stuck items using manual import process
- π₯ Triggers download client scan to refresh import status
- π Logs failures without removing items from queue (for manual resolution)
- π Reports the number of items successfully imported vs requiring manual attention
Import Issues Detected:
- "already imported"
- "episode file already imported"
- "one or more episodes expected"
- "missing from the release"
Note: This command only works with Sonarr (not Radarr) as download queue management is specific to Sonarr's import process.
# Run with Sonarr only
docker run -e SONARR_API_KEY="your-key" -e SONARR_URL="http://sonarr:8989" refresharr
# Run with Radarr only
docker run -e RADARR_API_KEY="your-key" -e RADARR_URL="http://radarr:7878" refresharr
# Run with both services
docker run \
-e SONARR_API_KEY="your-sonarr-key" -e SONARR_URL="http://sonarr:8989" \
-e RADARR_API_KEY="your-radarr-key" -e RADARR_URL="http://radarr:7878" \
refresharrThe service now generates comprehensive reports of missing files found during cleanup operations. Reports are automatically saved to the reports/ directory in JSON format and displayed in the terminal in human-readable format.
- JSON Export: Detailed reports saved as timestamped JSON files in
reports/directory - Terminal Display: Human-readable summary printed to console (unless
--no-reportflag is used) - Dry Run Support: Reports generated for both dry runs and actual cleanup operations
- Detailed Information: Includes media names, episode details, file paths, and timestamps
Each report includes:
- Service Type: Whether from Sonarr or Radarr
- Run Type: "dry-run" or "real-run"
- Generation Timestamp: When the report was created
- Total Missing Files: Count of missing files found
- File Details: For each missing file:
- Media name (series/movie title)
- Episode name and season/episode numbers (for TV shows)
- Complete file path
- Database file ID
- Processing timestamp
Terminal Display:
π MISSING FILES REPORT
==========================================
Generated: 2023-12-01T15:30:00Z
Service: sonarr
Run Type: dry-run
Total Missing Files: 3
Missing Files:
==========================================
1. Breaking Bad
Episode: S01E02 - Cat's in the Bag...
Missing File: /media/tv/Breaking Bad/Season 01/S01E02.mkv
File ID: 2001
Processed: 2023-12-01T15:30:15Z
2. The Office
Episode: S02E05 - Halloween
Missing File: /media/tv/The Office/Season 02/S02E05.mkv
File ID: 2456
Processed: 2023-12-01T15:30:32Z
3. Inception
Missing File: /media/movies/Inception (2010)/Inception.mkv
File ID: 3001
Processed: 2023-12-01T15:30:45Z
==========================================
π Report saved to: reports/sonarr-missing-files-report-dryrun-20231201-153000.json
JSON File Structure:
{
"generatedAt": "2023-12-01T15:30:00Z",
"runType": "dry-run",
"serviceType": "sonarr",
"totalMissing": 3,
"missingFiles": [
{
"mediaType": "series",
"mediaName": "Breaking Bad",
"episodeName": "Cat's in the Bag...",
"season": 1,
"episode": 2,
"filePath": "/media/tv/Breaking Bad/Season 01/S01E02.mkv",
"fileId": 2001,
"processedAt": "2023-12-01T15:30:15Z"
},
{
"mediaType": "movie",
"mediaName": "Inception",
"filePath": "/media/movies/Inception (2010)/Inception.mkv",
"fileId": 3001,
"processedAt": "2023-12-01T15:30:45Z"
}
]
}Sonarr Cleanup:
[INFO] Starting Refresharr v1.0.0 - Missing File Cleanup Service
[INFO] ================================================
[INFO] Starting sonarr missing file cleanup...
[INFO] ================================================
[INFO] β
Successfully connected to Sonarr
[INFO] Step 1: Fetching all series...
[INFO] Found 15 series
[INFO] Processing series 1/15 (ID: 123)
[INFO] Series: Breaking Bad
[INFO] Checking S1E1 (Episode ID: 1001)
[INFO] β
File exists: /media/tv/Breaking Bad/Season 01/S01E01.mkv
[INFO] Checking S1E2 (Episode ID: 1002)
[WARN] β MISSING: /media/tv/Breaking Bad/Season 01/S01E02.mkv
[INFO] ποΈ Deleting episode file record 2001...
[INFO] β
Successfully deleted episode file record (ID: 2001)
[INFO] ================================================
[INFO] Cleanup Summary:
[INFO] Total items checked: 342
[INFO] Missing files found: 5
[INFO] Records deleted: 5
[INFO]
[INFO] π Triggering refresh to update status...
[INFO] β
Refresh triggered successfully
[INFO] π Cleanup completed successfully!
Radarr Cleanup:
[INFO] Starting radarr missing file cleanup...
[INFO] ================================================
[INFO] β
Successfully connected to Radarr
[INFO] Step 1: Fetching all movies...
[INFO] Found 250 movies
[INFO] Processing movie 1/250 (ID: 456)
[INFO] Movie: The Matrix
[WARN] β MISSING: /media/movies/The Matrix (1999)/The Matrix (1999).mkv
[INFO] ποΈ Deleting movie file record 3001...
[INFO] β
Successfully deleted movie file record (ID: 3001)
[INFO] ================================================
[INFO] Cleanup Summary:
[INFO] Total items checked: 250
[INFO] Missing files found: 12
[INFO] Records deleted: 12
[INFO]
[INFO] π Triggering refresh to update status...
[INFO] β
Refresh triggered successfully
[INFO] π Cleanup completed successfully!
The codebase follows Go best practices with clear separation of concerns:
cmd/: Application entry pointsinternal/: Private application codepkg/: Public library code (models)main.go: Simple entry point for basic usage
The modular architecture makes it easy to add support for other *arr applications. The Client interface defines the contract that any new service must implement:
type Client interface {
GetName() string
TestConnection(ctx context.Context) error
// TV Shows (Sonarr)
GetAllSeries(ctx context.Context) ([]models.Series, error)
GetEpisodesForSeries(ctx context.Context, seriesID int) ([]models.Episode, error)
GetEpisodeFile(ctx context.Context, fileID int) (*models.EpisodeFile, error)
DeleteEpisodeFile(ctx context.Context, fileID int) error
UpdateEpisode(ctx context.Context, episode models.Episode) error
// Movies (Radarr)
GetAllMovies(ctx context.Context) ([]models.Movie, error)
GetMovieFile(ctx context.Context, fileID int) (*models.MovieFile, error)
DeleteMovieFile(ctx context.Context, fileID int) error
UpdateMovie(ctx context.Context, movie models.Movie) error
TriggerRefresh(ctx context.Context) error
}# Run tests
go test ./...
# Run with coverage
go test -cover ./...
# Verbose output
go test -v ./...- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Submit a pull request
This Go service replicates the functionality of tmp/sonarr-delete-missing.sh. The original script:
- Used bash with curl and jq
- Was Sonarr-specific
- Required manual execution
- Had limited error handling
The Go rewrite provides:
- β Better error handling and logging
- β Modular, extensible architecture
- β Cross-platform compatibility
- β Configuration management
- β Dry run capabilities
- β Progress reporting
[Add your license here]
- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
- π Wiki: GitHub Wiki
- Radarr support
- Web UI interface
- Docker containerization
- Automated scheduling
- Configuration file support
- Database backup before cleanup
- Webhook notifications
- Prometheus metrics
- Multi-instance support
- Lidarr support (music)
- Readarr support (books)