Music Sweeper¶
k8s: heezy namespace, one CronJob. No web UI, no Service, no hostname.
Source: heezy-containers/dockerfiles/music-sweeper/
Manifests: heezy-k8s/apps/music-sweeper/
Image: 025066240222.dkr.ecr.us-east-2.amazonaws.com/heezy-music-sweeper
Schedule: */30 * * * *
Last Updated: 2026-08-14
Why It Exists¶
soularr is the only thing in the media pipeline that moves files, and it only imports albums it grabbed inside its own run, tracked in memory. Three things fall through:
- Manual slskd searches. Nothing scans the downloads directory, and Lidarr
has no slskd download client configured, so a hand-grabbed album has no path
to
/musicat all. - Downloads that outlive the run that started them. soularr moves on; the transfer completes into a folder nobody revisits.
- Failed imports. These land in
failed_imports/and stay.
Measured on 2026-08-14, before the sweeper existed:
| Count | |
|---|---|
| Folders in the downloads PVC | 6,559 |
| Still carrying raw remote names (never handed to Lidarr) | 6,239 |
Carrying soularr's Artist - Album (Year) form |
320 |
In failed_imports/ |
129 |
Name-identical to a folder already under /music |
252 |
The worked example was Chris Hülsbeck: a monitored artist in Lidarr with all four Turrican Soundtrack Anthology volumes listed as monitored albums, 2.5GB of FLAC sitting in the PVC, and zero mentions of "Turrican" in soularr's logs. Hand-grabbed, so nothing was ever going to import it.
What One Run Does¶
- Ask slskd which folders it is currently writing into. If slskd cannot be reached the run stops here — "no answer" is not "nothing active", and moving a folder mid-transfer corrupts it.
- Take up to
MAX_ALBUMS(25) folders that are not in flight, not modified in the lastQUIET_MINUTES(15), and actually contain audio. - Delete slskd's ticks-suffixed re-grabs, keeping the largest of each collision.
- Identify the album from tags, falling back to parsing the folder name.
- If the library already holds it, delete the download.
- Otherwise rename to
Artist - Album (Year)and hand it to Lidarr viaDownloadedAlbumsScan. Success is measured by the audio being gone afterwards, not by the command status, which reportscompletedeven when it imported nothing. - If Lidarr declines, move the folder into
/musicdirectly. - Refresh only the Plex paths that changed, then post a summary to Discord.
Steps 6 and 7 are the reason both destinations exist. Lidarr-imported albums
land organized under /music/lists/<Artist>/; everything else lands flat at
/music/<Artist> - <Album> (<Year>)/. Both are inside Plex's library.
The slskd Ticks Suffix¶
When a file arrives whose name already exists in the destination, slskd appends .NET ticks rather than overwriting:
These are re-grabs of the same track, and an album carrying them will fail a Lidarr import because the track count no longer matches the release. Turrican Vol. 1 and Vol. 2 had 15 each.
The sweeper groups files by their ticks-stripped name and keeps the largest, tie-breaking to the unsuffixed original. A group with no ticks-suffixed member is two genuinely different files and is left alone.
Safety Properties¶
These are deliberate and worth preserving through any edit.
| Property | Mechanism |
|---|---|
| Unidentifiable albums are never deleted | album_key() returns None unless both artist and album normalize to something non-empty |
| A partial in the library cannot delete a full download | Library copy must have ≥ 90% as many tracks (COMPLETENESS_RATIO) |
| Destinations are never overwritten | An existing path gets a timestamp suffix |
| Blast radius is bounded | MAX_ALBUMS per run |
| Manual runs cannot collide with scheduled ones | Lock file under .sweeper/, stale after 3h |
| A pulled image cannot do damage | Dockerfile CMD passes --dry-run |
| One bad folder does not end the batch | Per-folder exception handling, reported in the summary |
Configuration¶
Full table in the app README. The ones that matter operationally:
| Variable | Default | Notes |
|---|---|---|
MAX_ALBUMS |
25 |
Folders per run. Raise to drain the backlog faster |
QUIET_MINUTES |
15 |
Ignore recently-modified folders |
DRY_RUN |
false |
Set true to report without changing anything |
DELETE_DUPLICATES |
true |
Set false to report duplicates and keep them |
LIDARR_DOWNLOADS_DIR |
/slskd-downloads |
The downloads PVC as Lidarr mounts it |
LIDARR_DOWNLOADS_DIR is the same trap soularr documents in its ConfigMap. The
sweeper mounts the PVC at /downloads, Lidarr at /slskd-downloads, and Lidarr
is handed a string it must resolve itself. Change one mount, change both.
The Library Index¶
Tag-reading a 40TB library every 30 minutes is not viable over NFS, so the
normalized artist|album index is cached to
$STATE_DIR/library-index.json (default /downloads/.sweeper/) and invalidated
per folder by mtime. The first run is slow; later runs re-read only what
changed.
Force a rebuild:
Operations¶
Trigger a run:
The Discord embed is the intended interface — counts per outcome, what moved, what was deleted and what it already had. Logs are the fallback:
It reports "slskd unreachable"¶
Expected during a gluetun VPN reconnect. Nothing was swept and nothing was harmed. If it persists, see Media Pipeline.
It deleted something it should not have¶
Every deletion names the library folder it matched against. If the match was
wrong, the fix is DELETE_DUPLICATES=false while the normalization in
app/naming.py is corrected — the matching rules are shared with
media-audit.py, so a fix belongs in both.
The backlog is not shrinking¶
Check the still queued count in the embed. At 25 albums per run on a
half-hourly schedule the ceiling is 1,200 a day, so a 6,000-folder backlog takes
about five days. Raise MAX_ALBUMS to move faster, at the cost of more NFS
traffic per tick.