Homelab Academy / Modules
Guided practice + break/fix + lab gate.
Module: Download Clients & Indexers
Activity type: Lab (Practice)
Lab risk: low
Objective: Explain how an ARR application assigns a download category when submitting a job to a download client.
This simulation creates mock downloader and ARR configuration files, a sample completed download, and a hardlinked library import. Every created or modified object remains beneath /opt/lab-classroom/class50/.
1
Create the isolated classroom layout
mkdir -p /opt/lab-classroom/class50/active/config /opt/lab-classroom/class50/active/data/downloads/incomplete /opt/lab-classroom/class50/active/data/downloads/complete/tv /opt/lab-classroom/class50/active/data/downloads/complete/movies /opt/lab-classroom/class50/active/data/media/tv/DemoSeries/Season-01 /opt/lab-classroom/class50/active/data/media/movies
The active directory acts as the complete lab environment. Downloads and media share the active/data parent while remaining operationally separate.
2
Create a mock download-client configuration
printf '%s\n' '{' ' "name": "mock-downloader",' ' "default_incomplete_path": "/opt/lab-classroom/class50/active/data/downloads/incomplete",' ' "categories": {' ' "tv": "/opt/lab-classroom/class50/active/data/downloads/complete/tv",' ' "movies": "/opt/lab-classroom/class50/active/data/downloads/complete/movies"' ' }' '}' > /opt/lab-classroom/class50/active/config/downloader.json
The tv and movies categories route completed data to distinct staging directories.
3
Create a mock Sonarr integration configuration
printf '%s\n' '{' ' "application": "Sonarr",' ' "download_client_host": "mock-downloader",' ' "category": "tv",' ' "completed_download_handling": true,' ' "reported_download_path": "/opt/lab-classroom/class50/active/data/downloads/complete/tv",' ' "library_root": "/opt/lab-classroom/class50/active/data/media/tv",' ' "remote_path_mappings": []' '}' > /opt/lab-classroom/class50/active/config/sonarr.json
No remote path mapping is present because the mock downloader and Sonarr configuration use the same absolute path.
4
Validate category and path separation
python3 - <<'PY'
import json
from pathlib import Path
base = Path('/opt/lab-classroom/class50/active/config')
downloader = json.loads((base / 'downloader.json').read_text())
sonarr = json.loads((base / 'sonarr.json').read_text())
category = sonarr['category']
reported = sonarr['reported_download_path']
library = sonarr['library_root']
assert category in downloader['categories'], 'ARR category is absent from downloader configuration'
assert downloader['categories'][category] == reported, 'Category path and reported path disagree'
assert reported != library, 'Download path and library root must be separate'
assert sonarr['completed_download_handling'] is True, 'Completed Download Handling is disabled'
assert sonarr['remote_path_mappings'] == [], 'A mapping is unnecessary in this shared namespace'
print('Category match: PASS')
print('Shared path namespace: PASS')
print('Download/library separation: PASS')
print('Unnecessary mapping absent: PASS')
PY
The validator checks the relationships among the category, completed path, library root, and mapping list.
5
Create a simulated completed download
printf '%s\n' 'Class 50 simulated media payload' > /opt/lab-classroom/class50/active/data/downloads/complete/tv/DemoSeries.S01E01.mkv
The file is harmless text with a media-style filename. It represents a completed downloader payload.
6
Simulate a hardlink import
ln /opt/lab-classroom/class50/active/data/downloads/complete/tv/DemoSeries.S01E01.mkv /opt/lab-classroom/class50/active/data/media/tv/DemoSeries/Season-01/DemoSeries-S01E01.mkv
The library receives an organized name while the downloader path remains available. The two names reference one underlying file.
7
Verify the import relationship
stat -c '%d:%i %h %n' /opt/lab-classroom/class50/active/data/downloads/complete/tv/DemoSeries.S01E01.mkv /opt/lab-classroom/class50/active/data/media/tv/DemoSeries/Season-01/DemoSeries-S01E01.mkv
Matching device and inode values prove that the entries are hardlinks. A link count of two confirms that two names reference the file.
8
Inspect the completed classroom tree
find /opt/lab-classroom/class50/active -maxdepth 8 -print
The output should show separate configuration, download, and library locations under the single permitted lab root.
| Symptom | Likely cause | Fix |
|---|---|---|
| The validator reports that the ARR category is absent from the downloader configuration. | The category configured in Sonarr does not exactly match a downloader category. | Make category spelling and case consistent. For this lab, both configurations must use tv. |
| The validator reports that the category path and reported path disagree. | The downloader routes the category to one directory while the mock ARR configuration expects another. | Set the reported path to the exact category destination or correct the downloader category destination. |
| The hardlink command reports that the destination already exists. | The import step was run more than once in the same active lab directory. | Use the rollback procedure to archive the active directory, then rerun the lab from step 1. |
| The hardlink command reports an invalid cross-device link. | The source and destination resolve to different filesystems. | Place downloads and media on one filesystem when hardlinks are required, or accept a copy-based import. In the prescribed lab layout, both locations should be under the same active/data tree. |
| A real ARR application reports that the completed path does not exist. | ARR cannot see the path reported by the download client, often because container mounts or path namespaces differ. | First align the storage mounts and internal paths. If the different path names are intentional and point to the same storage, add a host-specific remote path mapping. |
| ARR can see a completed file but cannot import it. | The ARR service identity lacks read access to the download or write access to the library. | Align service users and groups, directory ownership, and inherited permissions according to the platform. Avoid granting unrestricted access as a shortcut. |
| A torrent import consumes approximately another file's worth of allocated storage. | ARR copied the file instead of creating a hardlink, often because the locations are on different filesystems or are exposed as unrelated mounts. | Check ARR logs, compare filesystem device identifiers, and redesign mounts so downloads and media share a common storage parent if hardlinking is desired. |
| Files appear directly in the final library before ARR imports them. | The downloader's category destination was incorrectly set to the ARR library root. | Restore a dedicated download staging directory and keep the library root exclusively under ARR management. |
| The configuration file cannot be parsed as JSON. | A command was copied incompletely or the file was manually edited with invalid punctuation. | Archive the active lab using the rollback procedure and recreate the configuration from the documented steps. |
Grant the downloader write access only to its download workspace and any directories specifically required by its operation.
Grant Sonarr or Radarr read access to completed downloads and write access to their intended library roots.
Use a shared service group or equivalent access-control design instead of broad world-writable permissions.
Do not expose downloader or ARR administrative interfaces directly to untrusted networks.
Store downloader credentials and API keys in protected application secrets rather than lesson files or public configuration repositories.
Treat downloaded content as untrusted input. ARR import automation does not establish that a file is safe.
Keep download staging directories separate from media libraries so incomplete and unmanaged files are not served accidentally.
A remote path mapping is not an access-control feature and cannot compensate for missing mounts or insufficient permissions.
Review service logs for paths and filenames that may reveal private library information before sharing diagnostics.
Deactivate the lab without deleting its evidence and without changing anything outside the permitted class directory.
mkdir -p /opt/lab-classroom/class50/rollback-state
mv /opt/lab-classroom/class50/active /opt/lab-classroom/class50/rollback-state/active-class50
The active lab path is removed from service by moving it into a retained rollback-state directory. All artifacts remain available for review beneath /opt/lab-classroom/class50/.
After rollback, step 1 can create a new active directory. If active-class50 already exists in rollback-state, rename that retained directory to another unique name within /opt/lab-classroom/class50/ before repeating the rollback.