Sign inSign up

roomelephant/pluck

By roomelephant

•Updated 7 months ago

Watches your torrent client for downloads and automatically hardlinks them to the right directories.

Image
Content management system
Monitoring & observability
0

673

roomelephant/pluck repository overview

██████╗ ██╗     ██╗   ██╗ ██████╗██╗  ██╗
██╔══██╗██║     ██║   ██║██╔════╝██║ ██╔╝
██████╔╝██║     ██║   ██║██║     █████╔╝
██╔═══╝ ██║     ██║   ██║██║     ██╔═██╗
██║     ███████╗╚██████╔╝╚██████╗██║  ██╗
╚═╝     ╚══════╝ ╚═════╝  ╚═════╝╚═╝  ╚═╝

Lightweight, label-based torrent media sorter.

Pluck watches your torrent client. Matches labels to rules. Moves files. Nothing more.

Go License: MIT GitHub


⁠Why pluck?

If you self-host a media server (Jellyfin, Plex, Audiobookshelf, Calibre-web) alongside a torrent client, you need a way to route completed downloads to the right library folder. The *arr stack is powerful — but overkill if you just need "label X → folder Y".

That's what pluck does. Nothing more, nothing less.

Like Sonarr's import, but without the rest of Sonarr.

⁠Quick Start

⁠1. Create a rules file

Map torrent labels to destination folders. One rule per line: label:destination.

# rules.conf
audiobook:/data/media/audiobooks
ebook:/data/media/ebooks
music:/data/media/music
movie:/data/media/movies
tv:/data/media/tv

Labels are matched case-insensitively. Audiobook, AUDIOBOOK, and audiobook all match the same rule.

⁠2. Run with Docker Compose
services:
  pluck:
    image: roomelephant/pluck:latest
    restart: unless-stopped
    environment:
      - PLUCK_CLIENT=transmission
      - PLUCK_CLIENT_URL=http://transmission:9091
      - PLUCK_MODE=hardlink
      - PLUCK_WATCH_DIR=/data/downloads/complete
    volumes:
      - ./rules.conf:/config/rules.conf
      # Mount the common parent as a single volume (required for hardlink mode)
      - /data:/data

Available on Docker Hub⁠ (roomelephant/pluck) and GHCR⁠ (ghcr.io/room-elephant/pluck).

⁠3. Label your torrents

Add labels in your torrent client. When a download completes, pluck picks it up and places it in the matching directory.

Torrents with multiple labels are processed per label — each label gets its own placement.


⁠How It Works

┌─────────────────┐   3. Read Label    ┌─────────┐   5. Place File    ┌──────────────────────┐
│  Torrent Client │◀───────────────────│         │───────────────────▶│ /media/audiobooks/   │
│    (labels)     │                    │  pluck  │                    │ /media/ebooks/       │
└─────────────────┘   2. File added    │         │                    │ /media/music/        │
         │          ┌─────────────────▶│         │                    └──────────────────────┘
         │          │                  └─────────┘
         │ 1. Done  │                       │
         ▼          │                       │ 4. Check Rules
┌─────────────────┐ │                       ▼
│  Watch Dir      │─┘                  rules.conf
│  /data/downloads│                   label:directory
└─────────────────┘

Pluck detects completed torrents two ways:

TriggerHow
Filesystem watcherUses fsnotify⁠ to watch the downloads directory recursively. Fires after a 2-second debounce to let files settle.
Periodic rescanCatches torrents labeled after completion. Default: every 60 minutes, configurable via PLUCK_RESCAN_INTERVAL.

⁠Configuration

All configuration is done via environment variables:

VariableDefaultDescription
PLUCK_CLIENTtransmissionTorrent client type
PLUCK_CLIENT_URLhttp://transmission:9091Client URL
PLUCK_MODEhardlinkhardlink, symlink, or copy
PLUCK_WATCH_DIR/data/downloadsDirectory to watch for new files
PLUCK_RULES_FILE/config/rules.confPath to rules file
PLUCK_STATE_FILE/config/state.txtPath to state history file
PLUCK_RESCAN_INTERVAL3600Seconds between periodic rescans
PLUCK_DRY_RUNfalseLog actions without executing them
PLUCK_LOG_LEVELinfodebug, info, or error
⁠Rules File Format
# One rule per line: label:destination
# Labels are matched case-insensitively
# Lines starting with # are comments

audiobook:/data/media/audiobooks
ebook:/data/media/ebooks
music:/data/media/music
movie:/data/media/movies
tv:/data/media/tv
⁠File Placement Modes
ModeBehaviorCross-filesystem?Disk usage
hardlink (default)Hard-links files; source and destination share the same disk blocks❌ NoNone
symlinkCreates a symbolic link pointing to the source file✅ YesNone
copyCopies files to the destination✅ YesDoubles usage

Tip: Use hardlink when downloads and media share a filesystem. Use symlink across different filesystems. Use copy when you need the most reliability, but it doubles disk usage.

💡 Note on Ownership & Visibility: Pluck natively reads and replicates the exact User ID and Group ID (UID/GID) of your original downloads and applies them directly to the placed files and directories. This guarantees that your files retain perfect visibility and permissions for your media server.

⚠️ Hardlink mode: Docker treats each bind-mount as a separate filesystem. To allow hardlinking between your downloads and media folders, mount their common parent (e.g., /data:/data) as a single volume. Mounting them individually will cause a cross-device link error.

⚠️ Symlink mode: Ensure your media server container (Jellyfin, Plex, etc.) also mounts the downloads directory at the same path, so symlinks can resolve correctly.


⁠Supported Clients

ClientStatus
Transmission✅ Supported
qBittorrent🔜 Planned
Deluge🔜 Planned

⁠Dry Run

Test your rules without touching any files:

docker run --rm \
  -e PLUCK_DRY_RUN=true \
  -e PLUCK_LOG_LEVEL=debug \
  -v ./rules.conf:/config/rules.conf \
  -v /data/downloads:/data/downloads \
  ghcr.io/Room-Elephant/pluck:latest

⁠Building from Source

Docker:

docker build -t pluck .

Native (Go 1.26+):

go build -o pluck ./cmd/pluck

⁠Roadmap

  • qBittorrent support
  • Deluge support
  • Post-pluck webhook notifications (Audiobookshelf rescan, Discord, etc.)
  • Post-pluck custom scripts
  • Health check endpoint

⁠License

MIT⁠

Tag summary

Content type

Image

Digest

sha256:b4b47e273…

Size

2.7 MB

Last updated

7 months ago

docker pull roomelephant/pluck