Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
13 KiB
SABnzbd Integration
Status: ✅ Implemented
Free, open-source Usenet/NZB download client with comprehensive Web API. Industry standard for automation workflows.
Key Features
- Protocol: Usenet/NZB downloads (not torrents)
- Post-Processing: Automatic par2 repair, rar/zip extraction, cleanup
- Category Support: Per-category download paths
- API: RESTful JSON API with API key authentication
- Status: No hash extraction needed (NZB ID returned immediately)
API Endpoints
Base: http://sabnzbd:8080/api
Auth: API key parameter (apikey={key})
Format: All requests use output=json for JSON responses
GET /api?mode=version&output=json&apikey={key} - Get SABnzbd version POST /api (multipart: mode=addfile, nzbfile={binary}) - Add NZB by file upload (RMAB downloads NZB from Prowlarr, uploads to SABnzbd) GET /api?mode=queue&output=json&apikey={key} - Get active downloads GET /api?mode=history&limit=100&output=json&apikey={key} - Get completed/failed downloads GET /api?mode=pause&value={nzbId}&output=json&apikey={key} - Pause download GET /api?mode=resume&value={nzbId}&output=json&apikey={key} - Resume download GET /api?mode=queue&name=delete&value={nzbId}&del_files={0|1}&output=json&apikey={key} - Delete download from queue GET /api?mode=history&name=delete&value={nzbId}&del_files={0|1}&archive={0|1}&output=json&apikey={key} - Delete/archive download from history
archive=1(default): Move to hidden archive (preserves for troubleshooting)archive=0: Permanently delete from history GET /api?mode=get_config&output=json&apikey={key} - Get configuration (categories) GET /api?mode=set_config§ion=categories&keyword={cat}&value={path}&output=json&apikey={key} - Create/update category
Config
Required (database only, no env fallbacks):
download_client_type- Must be 'sabnzbd'download_client_url- SABnzbd Web UI URL (supports HTTP and HTTPS)download_client_password- API key (reuses password field)download_dir- Base download save path (joined with per-clientcustomPathif configured)
Optional (SSL/TLS):
download_client_disable_ssl_verify- Disable SSL certificate verification (boolean as string "true"/"false", default: "false")- Use when connecting to SABnzbd with self-signed certificates
- ⚠️ Security warning: Only use on trusted private networks
Optional (Remote Path Mapping):
download_client_remote_path_mapping_enabled- Enable path mapping (boolean)download_client_remote_path- Remote path prefix from SABnzbddownload_client_local_path- Local path prefix for ReadMeABook
Optional (SABnzbd-specific):
sabnzbd_category- Category name (default: 'readmeabook')
Validation: All required fields checked before service initialization. Path mapping fields validated when enabled.
Singleton Invalidation: Service uses singleton pattern. When settings change, singleton invalidated to force reload:
invalidateSABnzbdService()called after updating settings- Forces service to re-read database config
- Ensures category, credentials, and
customPathresolution are always current - Singleton getter resolves
customPathfrom client config (consistent with manager'screateService())
Category Management
Category: readmeabook (auto-created for all downloads)
Save Path Synchronization:
- Category created/updated on every download (matches qBittorrent behavior)
- Fetches SABnzbd's
complete_dirsetting via API to understand download location - Applies remote path mapping to translate RMAB's resolved download path to SABnzbd's perspective
- Calculates optimal category path (relative, absolute, or root)
- Resolved path includes per-client
customPathif configured (e.g.,/downloads/usenet)
Smart Path Calculation:
- Get SABnzbd's
complete_dirfrommisc.complete_dirconfig - Apply
PathMapper.reverseTransform()to RMAB's resolved download path (download_dir+customPath) - Compare transformed path to
complete_dir:- Match: Use empty string (downloads go to complete_dir root)
- Subdirectory: Use relative path (e.g.,
usenet) - Different: Use absolute path (e.g.,
/mnt/media/usenet)
Per-Client Custom Path:
- If
customPathis set (e.g.,usenet), category path calculated from/downloads/usenet - See download-clients.md for details
Post-Processing
Automatic (Built-in SABnzbd Features):
- Par2 Repair: Verifies and repairs damaged downloads
- Archive Extraction: Extracts .rar, .zip, .7z archives automatically
- Cleanup: Deletes .par2, .nfo, .nzb files after extraction
- Result:
downloadPathpoints to extracted directory (ready for file organizer)
Configuration: Post-processing level set to pp=3 (Repair + Unpack + Delete) on all downloads
Data Models
interface NZBInfo {
nzbId: string; // SABnzbd NZB ID
name: string;
size: number; // bytes
progress: number; // 0.0-1.0
status: NZBStatus;
downloadSpeed: number; // bytes/s
timeLeft: number; // seconds
category: string;
downloadPath?: string; // Available after completion
completedAt?: Date;
errorMessage?: string;
}
type NZBStatus = 'downloading' | 'queued' | 'paused' | 'extracting' | 'completed' | 'failed' | 'repairing';
interface QueueItem {
nzbId: string;
name: string;
size: number; // MB
sizeLeft: number; // MB
percentage: number; // 0-100
status: string; // "Downloading", "Paused", "Queued"
timeLeft: string; // "0:15:30" format
category: string;
priority: string;
}
interface HistoryItem {
nzbId: string;
name: string;
category: string;
status: string; // "Completed", "Failed"
bytes: string; // Size in bytes (as string)
failMessage: string;
storage: string; // Download path
completedTimestamp: string; // Unix timestamp
downloadTime: string; // Seconds
}
interface SABnzbdConfig {
version: string;
completeDir: string; // SABnzbd's configured complete download folder
categories: Array<{ name: string; dir: string }>;
}
NZB ID vs Torrent Hash
Key Difference: SABnzbd returns NZB ID immediately (no extraction needed)
qBittorrent:
- Returns "Ok." without hash
- Requires parsing magnet link or .torrent file
- Hash extraction needed for tracking
SABnzbd:
- Returns
nzo_idsarray immediately - NZB ID format:
SABnzbd_nzo_abc123xyz - No extraction needed, can track immediately
Database Storage:
- qBittorrent:
torrent_hashfield - SABnzbd:
nzb_idfield (new) - Both nullable, exactly one must be set
Status Tracking
Queue vs History:
- Queue: Active downloads (downloading, queued, paused)
- History: Completed or failed downloads
Monitoring Flow:
- Check queue for NZB ID
- If found: Parse progress, status, speed
- If not in queue: Check history
- If in history: Parse completion status or error
States:
Downloading→ Active downloadQueued→ Waiting in queuePaused→ Manually pausedExtracting/Unpacking→ Post-processing extractionRepairing/Verifying→ Post-processing par2 repairCompleted→ Successfully downloaded and extractedFailed→ Download or post-processing failed
Remote Path Mapping
Use Case: SABnzbd runs on different machine/container with different filesystem perspective.
Example Scenario:
- SABnzbd sees:
/mnt/usenet/complete - ReadMeABook sees:
/downloads - Mapping: Remote
/mnt/usenet/complete↔ Local/downloads
Bidirectional Path Mapping:
1. Outgoing (RMAB → SABnzbd): When setting category path
- RMAB's download path:
/downloads - Translated to SABnzbd's path:
/mnt/usenet/complete - Applied in
sabnzbd.service.tsviaPathMapper.reverseTransform() - Combined with
complete_dirdetection for optimal category configuration
2. Incoming (SABnzbd → RMAB): When processing completed downloads
- SABnzbd reports:
/mnt/usenet/complete/Audiobook.Name - Translated to RMAB's path:
/downloads/Audiobook.Name - Applied in
monitor-download.processor.tsviaPathMapper.transform() - Ensures RMAB can find and organize files
Path Transformation Examples:
// Outgoing: RMAB → SABnzbd (when setting category)
localPath = "/downloads"
config = { remotePath: "/mnt/usenet/complete", localPath: "/downloads" }
remotePath = PathMapper.reverseTransform(localPath, config)
// Result: "/mnt/usenet/complete"
// Incoming: SABnzbd → RMAB (when processing completion)
sabPath = "/mnt/usenet/complete/Audiobook.Name"
config = { remotePath: "/mnt/usenet/complete", localPath: "/downloads" }
organizePath = PathMapper.transform(sabPath, config)
// Result: "/downloads/Audiobook.Name"
Implementation: Uses PathMapper utility (same as qBittorrent)
Fixed Issues ✅
1. API Key Authentication - Uses apikey parameter (not username/password)
2. Immediate NZB ID - No hash extraction needed (returned by API)
3. Post-Processing Tracking - Monitors extracting/repairing states
4. Queue vs History Logic - Checks queue first, falls back to history
5. SSL Certificate Errors - Optional SSL verification disable for self-signed certs
6. Category path not synced with complete_dir - SABnzbd downloads to its own complete_dir, not RMAB's path. Fixed by:
- Fetching SABnzbd's
complete_dirfrom config API (misc.complete_dir) - Calculating relative or absolute category path based on path comparison
- Applying remote path mapping before comparison
- Syncing category path on every download (same as qBittorrent) 7. Remote path mapping not applied - Paths weren't translated between RMAB and SABnzbd perspectives. Fixed by:
- Adding
PathMappingConfigto SABnzbd service constructor - Applying
reverseTransform()when setting category path (outgoing) - Applying
transform()when processing completed downloads (incoming) - Using same
PathMapperutility as qBittorrent for consistency
Automatic Cleanup
Per-Indexer Configuration:
- Usenet indexers have "Remove After Processing" option (default: enabled)
- When enabled, NZB downloads are automatically cleaned up after files are organized
- Saves disk space by removing completed download files
Three-Stage Cleanup Process:
-
Download Cleanup: Deletes download directory/files using
fs.rm()- Removes extracted files from category download directory
- Handles both single files and directories recursively
- Gracefully handles already-deleted files (ENOENT)
-
Parent Directory Cleanup: Removes empty parent directories up to
download_dir- Uses
removeEmptyParentDirectories()utility fromcleanup-helpers.ts - Walks up directory tree removing empty folders (e.g., empty category folder)
- Never deletes
download_diritself (protected boundary) - Stops at first non-empty directory
- Gracefully handles permission errors and race conditions
- Uses
-
SABnzbd Archive: Archives NZB from history (hides from UI)
- Uses SABnzbd's archive feature (default:
archive=1) - Preserves job in hidden archive for troubleshooting/auditing
- Does NOT permanently delete from history
- Does NOT attempt queue deletion (if still in queue, something went wrong)
- Uses SABnzbd's archive feature (default:
Implementation:
- Location:
organize-files.processor.ts - After file organization completes, checks if indexer has
removeAfterProcessingenabled - Filesystem cleanup performed first (critical for disk space)
- SABnzbd archive performed second (UI cleanup)
- Non-blocking: logs warnings but doesn't fail the job if cleanup fails
Why Archive Instead of Delete:
- Preserves download history for troubleshooting
- Maintains records for duplicate detection
- Allows reviewing past downloads if issues arise
- Can be viewed in SABnzbd by toggling "Show Archive" in history
Comparison: SABnzbd vs qBittorrent
| Feature | SABnzbd | qBittorrent |
|---|---|---|
| Protocol | Usenet/NZB | BitTorrent |
| Auth | API key only | Username + Password |
| ID Format | NZB ID (immediate) | Torrent hash (extracted) |
| Post-Processing | Automatic (par2, extraction) | None (manual) |
| Seeding | N/A (Usenet is not P2P) | Required (tracker) |
| Categories | Path-based (relative to complete_dir) | Path + tag-based |
| File Handling | Auto-extracts archives | Downloads as-is |
| Cleanup | Automatic (optional, per-indexer) | Seeding time based |
| Path Mapping | ✅ Bidirectional (same as qBit) | ✅ Bidirectional |
| Category Sync | ✅ Every download | ✅ Every download |
NZB Download Proxy
RMAB proxies NZB files — SABnzbd does not need network access to Prowlarr.
Prowlarr returns download URLs that point back to itself (proxy URLs like http://prowlarr:9696/3/download?apikey=...&link=...).
RMAB downloads the NZB file content from that URL, then uploads it to SABnzbd via mode=addfile (multipart POST).
This matches qBittorrent's pattern where RMAB downloads .torrent files and uploads the binary content.
Result: Download clients only need network access to RMAB. No direct Prowlarr connectivity required.
Tech Stack
- axios (HTTP client, NZB file download)
- form-data (multipart file upload to SABnzbd)
- Node.js https (SSL/TLS agent)
- JSON API responses
Related
- See File Organization for post-download processing
- See qBittorrent Integration for torrent alternative