unRAID Mover and qBittorrent
When you use the unRAID cache drive for your /data/torrents share while torrents are actively seeding in qBittorrent, the unRAID mover cannot move files. This happens because the files are still in use, which would break the hard links.
Using the instructions below, you can move files using the qBittorrent API with the qBit-Mover script.
How the qBit-Mover Script Works
The qBit-Mover script doesn't move files itself. It only pauses and resumes torrents, and it can trigger the unRAID mover or Mover Tuning.
This guide explains two ways to use the mover script:
- Option 1: Combined with the Mover Tuning Plugin (Recommended)
- Option 2: Using User Scripts
Option 1
This option uses the Mover Tuning plugin to:
- Pause torrents within a specific age range that are on your cache drive
- Resume the torrents after the unRAID mover finishes
It also offers these features:
- Automatically install and update the qbittorrent-api module (REQUIRED)
- Automatically download the qBit-Mover script (REQUIRED)
-
qBit-Manage integration (OPTIONAL)
- Stop qBit-Manage before qBit-Mover runs
-
Start qBit-Manage after qBit-Mover completes or after fclones finishes
If qBit-Manage runs while files are moving from cache to your array, it may incorrectly mark your files as NoHL. We strongly recommend enabling this option to prevent this issue.
-
Automatically download fclones (OPTIONAL)
- Run fclones (Replace copies with hardlinks)
- Automatically set the correct unRAID User/Group and permissions
Option 2
This option runs the script from User Scripts to:
- Pause torrents within a specific age range that are on your cache drive
- Run/trigger the unRAID mover
- Resume the torrents after the unRAID mover finishes
Requirements
Important: Disable Pre-allocation in qBittorrent
Go to qBittorrent → Options → Downloads and disable this option:
Pre-allocate disk space for all files
When this option is enabled, it keeps the reserved space locked (in use) until you quit qBittorrent.
Tips & Info
Tip
- Don't disable the mover from Settings → Scheduler → Mover Settings. Instead, you could set the mover to run once a month, one minute after you run the qBit Mover script. The mover shouldn't run because it's already running.
- If you're also using Mover Tuning, don't disable the mover from running on a schedule—this could completely disable it.
- We recommend using Mover Tuning. If you do, make sure Move files that are greater than this many days old matches the number of days you set in the qBit-Mover script or config.
Info
The screenshots below are EXAMPLES to show you how things should look and where to add data. They are NOT always 100% accurate reflections of the actual data or the exact values you need.
- Always follow the recommendations in this guide.
- If you have questions or aren't sure about something, click the chat badge to join the Discord Channel where you can ask questions directly.
Option 1: Mover Tuning
This option expects that you follow the guide's suggested paths as described in this section.
Install the following plugins:
- Python 3 for unRAID (unRAID Plugin)
- Mover Tuning (unRAID Plugin)
Install the following container (Optional but suggested):
- qBit-Manage
For this option, you only need to download three files and place them in /mnt/user/appdata/qbt-mover/:
-
mover-tuning-start.sh - The script that runs before the mover starts.
mover-tuning-start.sh - [Click to show/hide]
#!/bin/bash set -euo pipefail # Exit on error, undefined variables, and pipe failures # ======================================= # Script: qBittorrent Cache Mover - Start # Updated: 20251112 # ======================================= # Get the directory where the script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Source the config from the same directory source "$SCRIPT_DIR/mover-tuning.cfg" readonly VENV_PATH="${QBIT_MOVER_PATH}.venv" readonly MOVER_SCRIPT="${QBIT_MOVER_PATH}mover.py" readonly MOVER_URL="https://raw.githubusercontent.com/StuffAnThings/qbit_manage/develop/scripts/mover.py" # Notification delay in seconds (helps ensure all notifications appear in Unraid) NOTIFICATION_DELAY=2 # ================================ # UTILITY FUNCTIONS # ================================ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } error() { log "ERROR: $*" >&2 exit 1 } notify() { local subject="$1" local description="$2" local notify_cmd="/usr/local/emhttp/plugins/dynamix/scripts/notify" if [[ -x "$notify_cmd" ]]; then "$notify_cmd" -s "$subject" -d "$description" # Add delay after each notification to prevent dropping sleep "$NOTIFICATION_DELAY" fi } check_command() { command -v "$1" &> /dev/null } set_ownership() { chown -R nobody:users "$1" 2>/dev/null || log "⚠ Warning: Could not set ownership for $1" } # ================================ # AUTO INSTALLER FUNCTION # ================================ run_auto_installer() { log "========================================" log "Running qBit-Api and qBit-Mover Auto Installer" log "========================================" # Create QBIT_MOVER_PATH directory if needed if [[ ! -d "$QBIT_MOVER_PATH" ]]; then mkdir -p "$QBIT_MOVER_PATH" || error "Failed to create $QBIT_MOVER_PATH" set_ownership "$QBIT_MOVER_PATH" log "✓ Created $QBIT_MOVER_PATH" fi # Create virtual environment if needed if [[ ! -d "$VENV_PATH" ]]; then log "Creating virtual environment..." python3 -m venv "$VENV_PATH" || error "Failed to create virtual environment" set_ownership "$VENV_PATH" log "✓ Virtual environment created" else log "✓ Virtual environment exists" fi # Activate virtual environment # shellcheck source=/dev/null source "${VENV_PATH}/bin/activate" || error "Failed to activate virtual environment" # Upgrade pip if needed log "Checking pip version..." if pip3 install --upgrade pip --quiet 2>&1 | grep -q "Successfully installed"; then log "✓ Pip upgraded to $(pip3 --version | awk '{print $2}')" set_ownership "$VENV_PATH" else log "✓ Pip is up to date" fi # Install/upgrade qbittorrent-api if python3 -c "import qbittorrentapi" 2>/dev/null; then log "✓ qbittorrent-api installed ($(pip3 show qbittorrent-api 2>/dev/null | awk '/Version:/ {print $2}'))" # Check for updates if pip3 install --dry-run --upgrade qbittorrent-api 2>&1 | grep -q "Would install"; then log "Upgrading qbittorrent-api..." pip3 install qbittorrent-api --upgrade --quiet || log "⚠ Warning: Failed to upgrade qbittorrent-api" set_ownership "$VENV_PATH" log "✓ qbittorrent-api upgraded" else log "✓ qbittorrent-api is up to date" fi else log "Installing qbittorrent-api..." pip3 install qbittorrent-api --quiet || error "Failed to install qbittorrent-api" set_ownership "$VENV_PATH" log "✓ qbittorrent-api installed" fi deactivate # Download mover.py if needed if [[ ! -f "$MOVER_SCRIPT" ]]; then log "Downloading mover.py..." if curl -sSL "$MOVER_URL" -o "$MOVER_SCRIPT"; then chmod +x "$MOVER_SCRIPT" set_ownership "$MOVER_SCRIPT" log "✓ mover.py downloaded" else error "Failed to download mover.py" fi else log "✓ mover.py exists" fi log "========================================" log "Auto Installer completed" log "========================================" } # ================================ # VALIDATION # ================================ validate_config() { log "Validating configuration..." # Check required commands for cmd in python3 date curl; do check_command "$cmd" || error "$cmd is not installed" done # Validate docker if needed if [[ "$ENABLE_QBIT_MANAGE" == true ]]; then check_command docker || error "docker is required when ENABLE_QBIT_MANAGE=true" fi # Validate settings [[ "$DAYS_FROM" -ge 2 ]] || error "DAYS_FROM must be at least 2" [[ "$DAYS_TO" -ge "$DAYS_FROM" ]] || error "DAYS_TO must be >= DAYS_FROM" [[ -d "$CACHE_MOUNT" ]] || error "Cache mount does not exist: $CACHE_MOUNT" } # ================================ # PROCESS QBITTORRENT INSTANCE # ================================ process_qbit_instance() { local name="$1" host="$2" user="$3" password="$4" log "Processing $name..." # Determine Python command local python_cmd if [[ -f "${VENV_PATH}/bin/python3" ]]; then python_cmd="${VENV_PATH}/bin/python3" elif python3 -c "import qbittorrentapi" 2>/dev/null; then python_cmd="python3" else log "✗ qbittorrent-api not found for $name" return 1 fi # Run mover script if $python_cmd "$MOVER_SCRIPT" \ --pause \ --host "$host" \ --user "$user" \ --password "$password" \ --cache-mount "$CACHE_MOUNT" \ --days_from "$DAYS_FROM" \ --days_to "$DAYS_TO" 2>&1 | while IFS= read -r line; do log " $line" done; then log "✓ Successfully processed $name" notify "$name" "Paused @ $(date +%H:%M:%S)" return 0 else log "✗ Failed to process $name" return 1 fi } # ================================ # MAIN EXECUTION # ================================ main() { local failed_instances=0 readonly date_from=$(date --date="$DAYS_FROM day ago" +%F) log "========================================" log "qBittorrent Cache Mover Started" log "Date range: $DAYS_FROM-$DAYS_TO days (from $date_from)" log "========================================" # Run auto installer if enabled [[ "$ENABLE_AUTO_INSTALLER" == true ]] && run_auto_installer # Validate configuration validate_config [[ -f "$MOVER_SCRIPT" ]] || error "mover.py not found at: $MOVER_SCRIPT" # Stop qBit-Manage if enabled if [[ "$ENABLE_QBIT_MANAGE" == true ]]; then log "Stopping $QBIT_MANAGE_CONTAINER..." if docker stop "$QBIT_MANAGE_CONTAINER" &> /dev/null; then log "✓ Stopped qBit-Manage" notify "qBit-Manage" "Stopped @ $(date +%H:%M:%S)" sleep "$QBIT_MANAGE_WAIT" else log "⚠ Warning: Failed to stop $QBIT_MANAGE_CONTAINER" fi fi # Process instances process_qbit_instance "$QBIT_NAME_1" "$QBIT_HOST_1" "$QBIT_USER_1" "$QBIT_PASS_1" || ((failed_instances++)) if [[ "$ENABLE_QBIT_2" == true ]]; then process_qbit_instance "$QBIT_NAME_2" "$QBIT_HOST_2" "$QBIT_USER_2" "$QBIT_PASS_2" || ((failed_instances++)) fi # Summary log "========================================" if [[ $failed_instances -eq 0 ]]; then log "✓ All operations completed successfully" exit 0 else log "⚠ Completed with $failed_instances failed instance(s)" exit 1 fi } # Run main function main -
mover-tuning-end.sh - The script that runs after the mover finishes.
mover-tuning-end.sh - [Click to show/hide]
#!/bin/bash set -euo pipefail # Exit on error, undefined variables, and pipe failures # ===================================== # Script: qBittorrent Cache Mover - End # Updated: 20251112 # ===================================== # Get the directory where the script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Source the config from the same directory source "$SCRIPT_DIR/mover-tuning.cfg" # Notification delay in seconds (helps ensure all notifications appear in Unraid) NOTIFICATION_DELAY=2 # ================================ # UTILITY FUNCTIONS # ================================ log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" } error() { log "ERROR: $*" >&2 exit 1 } notify() { local subject="$1" local description="$2" local notify_cmd="/usr/local/emhttp/plugins/dynamix/scripts/notify" if [[ -x "$notify_cmd" ]]; then "$notify_cmd" -s "$subject" -d "$description" # Add delay after each notification to prevent dropping sleep "$NOTIFICATION_DELAY" fi } # ================================ # AUTO-INSTALLER FUNCTIONS # ================================ install_fclones_binary() { log "Installing/updating fclones binary..." # fclones configuration local FCLONES_BIN="/usr/local/bin/fclones" local BOOT_DIR="/boot/config/plugins/fclones/usr/bin" local GO_FILE="/boot/config/go" local DEFAULT_VERSION="0.35.0" # Current installed version local CURRENT_VERSION="" if [[ -x "$FCLONES_BIN" ]]; then CURRENT_VERSION=$($FCLONES_BIN --version 2>/dev/null | awk '{print $2}') log "✓ Found fclones version $CURRENT_VERSION" else log "✗ fclones not found" fi # Check for curl or wget local GITHUB_API_CMD if command -v curl >/dev/null 2>&1; then GITHUB_API_CMD="curl -s https://api.github.com/repos/pkolaczk/fclones/releases/latest" elif command -v wget >/dev/null 2>&1; then GITHUB_API_CMD="wget -qO- https://api.github.com/repos/pkolaczk/fclones/releases/latest" else log "✗ Neither curl nor wget is available" return 1 fi # Fetch latest release from GitHub local LATEST_VERSION LATEST_VERSION=$($GITHUB_API_CMD | grep -Po '"tag_name": "\K.*?(?=")') if [[ -z "$LATEST_VERSION" ]]; then log "⚠ Could not fetch latest release, using default version $DEFAULT_VERSION" LATEST_VERSION="$DEFAULT_VERSION" else log "Latest fclones release: $LATEST_VERSION" fi # Compare and install if missing or outdated if [[ "$CURRENT_VERSION" != "$LATEST_VERSION" ]]; then log "Installing/updating fclones to $LATEST_VERSION..." local TMP_DIR TMP_DIR=$(mktemp -d) # Remove leading 'v' from filename local VERSION_NO_V="${LATEST_VERSION#v}" local DOWNLOAD_URL="https://github.com/pkolaczk/fclones/releases/download/$LATEST_VERSION/fclones-$VERSION_NO_V-linux-glibc-x86_64.tar.gz" wget -O "$TMP_DIR/fclones.tar.gz" "$DOWNLOAD_URL" if [[ $? -ne 0 ]]; then log "✗ Failed to download fclones from $DOWNLOAD_URL" rm -rf "$TMP_DIR" return 1 fi tar -xzf "$TMP_DIR/fclones.tar.gz" -C "$TMP_DIR" mkdir -p "$BOOT_DIR" cp "$TMP_DIR/usr/bin/fclones" "$BOOT_DIR/fclones" chmod +x "$BOOT_DIR/fclones" # Copy to /usr/local/bin immediately cp "$BOOT_DIR/fclones" /usr/local/bin/fclones chmod +x /usr/local/bin/fclones # Add boot-time copy and PATH setup if not already in /boot/config/go if ! grep -q "fclones boot-time setup" "$GO_FILE"; then if [ ! -w "$GO_FILE" ]; then log "✗ Cannot write to $GO_FILE. Please check permissions." rm -rf "$TMP_DIR" return 1 fi echo "" >> "$GO_FILE" echo "# fclones boot-time setup" >> "$GO_FILE" echo "export PATH=/usr/local/bin:\$PATH" >> "$GO_FILE" echo "cp $BOOT_DIR/fclones /usr/local/bin/fclones" >> "$GO_FILE" fi rm -rf "$TMP_DIR" log "✓ fclones $VERSION_NO_V installed successfully" return 0 else log "✓ fclones is up to date ($CURRENT_VERSION)" return 0 fi } install_fclones_script() { log "Installing fclones.sh script..." local raw_script_url="https://gist.githubusercontent.com/BaukeZwart/b570ce6b6165c4f0b64c5b98d9d3af1e/raw" local script_path="${QBIT_MOVER_PATH}fclones.sh" # Create directory if needed mkdir -p "$QBIT_MOVER_PATH" # Download script if command -v curl &> /dev/null; then curl -fsSL "$raw_script_url" -o "$script_path" || { log "✗ Failed to download fclones.sh script" return 1 } elif command -v wget &> /dev/null; then wget -q "$raw_script_url" -O "$script_path" || { log "✗ Failed to download fclones.sh script" return 1 } else log "✗ Neither curl nor wget is available" return 1 fi # Set permissions chmod +x "$script_path" chown nobody:users "$script_path" 2>/dev/null || \ log "⚠ Warning: Could not set ownership to nobody:users" log "✓ fclones.sh script installed at $script_path" return 0 } check_and_install_fclones() { log "Checking fclones installation..." local need_install=false # Check binary if ! command -v fclones &> /dev/null; then log "✗ fclones binary not found" if ! install_fclones_binary; then notify "fclones Auto-Installer" "Failed to install binary @ $(date +%H:%M:%S)" return 1 fi need_install=true else # Check if update is needed if ! install_fclones_binary; then log "⚠ Failed to check/update fclones binary" fi fi # Check script if [[ ! -f "${QBIT_MOVER_PATH}fclones.sh" ]]; then log "✗ fclones.sh script not found" if ! install_fclones_script; then notify "fclones Auto-Installer" "Failed to install script @ $(date +%H:%M:%S)" return 1 fi need_install=true else log "✓ fclones.sh script found" fi if [[ "$need_install" == true ]]; then log "✓ fclones installation completed" notify "fclones Auto-Installer" "Installation completed @ $(date +%H:%M:%S)" fi return 0 } # ================================ # VALIDATION # ================================ validate_config() { log "Validating configuration..." # Check required commands local missing_cmds=() for cmd in python3 date; do command -v "$cmd" &> /dev/null || missing_cmds+=("$cmd") done if [[ ${#missing_cmds[@]} -gt 0 ]]; then error "Missing required commands: ${missing_cmds[*]}" fi # Check docker if needed if [[ "$ENABLE_QBIT_MANAGE" == true ]] && ! command -v docker &> /dev/null; then error "docker is required when ENABLE_QBIT_MANAGE=true" fi # Validate paths and values [[ "$DAYS_FROM" -ge 2 ]] || error "DAYS_FROM must be at least 2" [[ "$DAYS_TO" -ge "$DAYS_FROM" ]] || error "DAYS_TO must be >= DAYS_FROM" [[ -d "$CACHE_MOUNT" ]] || error "Cache mount not found: $CACHE_MOUNT" [[ -f "${QBIT_MOVER_PATH}mover.py" ]] || error "mover.py not found: ${QBIT_MOVER_PATH}mover.py" # Validate duplicate finder if enabled if [[ "$ENABLE_DUPLICATE_FINDER" == true ]]; then if [[ "$ENABLE_AUTO_INSTALLER" == true ]]; then check_and_install_fclones || return 1 else [[ -f "${QBIT_MOVER_PATH}fclones.sh" ]] || \ error "Duplicate finder script not found: ${QBIT_MOVER_PATH}fclones.sh" [[ -x "${QBIT_MOVER_PATH}fclones.sh" ]] || \ error "Duplicate finder script not executable: ${QBIT_MOVER_PATH}fclones.sh" fi fi log "✓ Validation completed" } # ================================ # PROCESS QBITTORRENT INSTANCE # ================================ process_qbit_instance() { local name="$1" local host="$2" local user="$3" local password="$4" log "Processing $name..." # Determine Python command local python_cmd if [[ -f "${QBIT_MOVER_PATH}.venv/bin/python3" ]]; then python_cmd="${QBIT_MOVER_PATH}.venv/bin/python3" log "✓ Using virtual environment" elif python3 -c "import qbittorrentapi" 2>/dev/null; then python_cmd="python3" log "✓ Using system Python" else log "✗ qbittorrent-api not found" return 1 fi # Execute mover script if "$python_cmd" "${QBIT_MOVER_PATH}mover.py" \ --resume \ --host "$host" \ --user "$user" \ --password "$password" \ --days_from "$DAYS_FROM" \ --days_to "$DAYS_TO"; then log "✓ Successfully resumed torrents for $name" notify "$name" "Resumed @ $(date +%H:%M:%S)" return 0 else log "✗ Failed to resume torrents for $name" return 1 fi } # ================================ # MAIN EXECUTION # ================================ main() { local failed_instances=0 local date_str date_str=$(date --date="$DAYS_FROM day ago" +%F) log "========================================" log "Starting torrent resume process" log "Age range: $DAYS_FROM-$DAYS_TO days (from $date_str)" log "========================================" # Validate configuration validate_config || exit 1 # Process primary instance process_qbit_instance "$QBIT_NAME_1" "$QBIT_HOST_1" "$QBIT_USER_1" "$QBIT_PASS_1" || \ ((failed_instances++)) # Process secondary instance if enabled if [[ "$ENABLE_QBIT_2" == true ]]; then log "Processing secondary instance..." process_qbit_instance "$QBIT_NAME_2" "$QBIT_HOST_2" "$QBIT_USER_2" "$QBIT_PASS_2" || \ ((failed_instances++)) else log "Secondary instance disabled" fi # Run duplicate finder if enabled if [[ "$ENABLE_DUPLICATE_FINDER" == true ]]; then log "Running duplicate finder..." if bash "${QBIT_MOVER_PATH}fclones.sh"; then log "✓ $DUPLICATE_FINDER_NAME completed" notify "$DUPLICATE_FINDER_NAME" "Completed @ $(date +%H:%M:%S)" else log "⚠ $DUPLICATE_FINDER_NAME failed" notify "$DUPLICATE_FINDER_NAME" "Failed @ $(date +%H:%M:%S)" fi fi # Start qBit-Manage if enabled if [[ "$ENABLE_QBIT_MANAGE" == true ]]; then log "Starting qBit-Manage container..." if docker start "$QBIT_MANAGE_CONTAINER" &> /dev/null; then log "✓ qBit-Manage started" notify "qBit-Manage" "Started @ $(date +%H:%M:%S)" else log "⚠ Failed to start qBit-Manage" fi fi # Summary log "========================================" if [[ $failed_instances -eq 0 ]]; then log "✓ All operations completed successfully" exit 0 else log "⚠ Completed with $failed_instances failed instance(s)" exit 1 fi } # Run main function main -
mover-tuning.cfg - This config file holds all the user variables used by the other scripts.
Read and edit the instructions inside the script.
mover-tuning.cfg - [Click to show/hide]
# ============================================= # <----- mover-tuning start/end settings -----> # ============================================= # Auto-installer configuration (Optional) readonly ENABLE_AUTO_INSTALLER=true # Set to false to disable qBit-Api and qBit-Mover auto installer # qBit-Mover Settings # >>> NOTE: Setting "DAYS_FROM" below 2 days may not work properly <<< readonly DAYS_FROM=25 # How old torrents must be (in days) before they're paused and moved (Must be at least 2 days) readonly DAYS_TO=99 # Maximum age (days) for torrent selection readonly CACHE_MOUNT="/mnt/cache/" # Cache mount point in Unraid readonly QBIT_MOVER_PATH="/mnt/user/appdata/qbt-mover/" # Path to mover.py # Primary qBittorrent instance (REQUIRED) readonly QBIT_NAME_1="qBit-Movies" # qBittorrent instance name readonly QBIT_HOST_1="192.168.2.200:8800" # qBittorrent host:port readonly QBIT_USER_1="admin" # qBittorrent username readonly QBIT_PASS_1="qbt1-password" # qBittorrent password # Secondary qBittorrent instance (OPTIONAL) readonly ENABLE_QBIT_2=false # Set to true to enable secondary instance readonly QBIT_NAME_2="qBit-TV" # qBittorrent instance name readonly QBIT_HOST_2="192.168.2.200:8811" # qBittorrent host:port readonly QBIT_USER_2="admin" # qBittorrent username readonly QBIT_PASS_2="qbt2-password" # qBittorrent password # qBit-Manage integration (OPTIONAL) readonly ENABLE_QBIT_MANAGE=true # Set to false to disable qBit-Manage readonly QBIT_MANAGE_CONTAINER="qbit-manage" # qBit-Manage Docker container name readonly QBIT_MANAGE_WAIT=5 # Wait time (seconds) after stopping qBit-Manage # Duplicate finder script (OPTIONAL) readonly ENABLE_DUPLICATE_FINDER=true # Set to false to disable duplicate finder readonly DUPLICATE_FINDER_NAME="Duplicate Finder" # Name for logging/notifications # ========================================= # <----- fclones run script settings -----> # ========================================= # Path to fclones executable # # OPTION 1: System-wide installation (Recommended) # If you installed fclones system-wide (e.g., via the mover-tuning-end script) # it will be available in your PATH. # In this case, simply use: # FCLONES_PATH="fclones" # # OPTION 2: Custom binary location # If you downloaded the fclones binary manually: # 1. Download from: https://github.com/pkolaczk/fclones/releases # 2. Extract the archive # 3. Make the binary executable: chmod +x fclones # 4. Place it in a directory of your choice # 5. Set the full path below, for example: # FCLONES_PATH="/mnt/user/apps/fclones/fclones" # To verify your installation, run: $FCLONES_PATH --version readonly FCLONES_PATH="fclones" # fclones arguments for the group command # Note: --cache, file paths, and --name patterns will be added automatically readonly FCLONES_ARGS="--one-fs --hidden --follow-links" # File patterns to match (space-separated) readonly FILE_PATTERNS="*.mkv *.mp4 *.avi" # Maximum number of files to display per folder in notifications # Set to 0 for unlimited, or any positive number to limit readonly MAX_FILES_DISPLAY=5 # Display full filenames including file extensions in notifications # Set to true to show full filename with extension, false to show without extension readonly SHOW_FILE_EXTENSIONS=false # Maximum filename length in notifications before truncating # Filenames longer than this will be truncated with "..." readonly MAX_FILENAME_LENGTH=56 # Log file settings LOG_PATH="/mnt/user/appdata/qbt-mover/fclones-logs" readonly LOG_RETENTION_DAYS=5 # Search path pairs - Add or modify your folder pairs here # Format: folders["Display Name"]="source_path target_path" declare -A folders folders["Series"]="/mnt/user/data/torrents/tv/ /mnt/user/data/media/tv/" folders["Movies"]="/mnt/user/data/torrents/movies/ /mnt/user/data/media/movies/" # folders["Series-xseed"]="/mnt/user/data/torrents/tv/ /mnt/user/data/torrents/series_linkdir/" # folders["Movies-xseed"]="/mnt/user/data/torrents/movies/ /mnt/user/data/torrents/movies_linkdir/"
Permissions
Once you've downloaded all the scripts, make sure the permissions are correct and that the scripts are executable. You can do this from a terminal with the following command:
chown -R nobody:users /mnt/user/appdata/qbt-mover/
chmod -R a=,a+rX,u+w,g+w /mnt/user/appdata/qbt-mover/
chmod +x /mnt/user/appdata/qbt-mover/mover-tuning-start.sh
chmod +x /mnt/user/appdata/qbt-mover/mover-tuning-end.sh
Mover Tuning Settings
We'll only cover the Mover Tuning settings that are important for qBit-Mover, not every single setting.
In your unRAID Dashboard, go to the Settings tab and select Scheduler in the User Preferences section.
Mover Settings
In the Scheduler under Mover Settings, first set when the mover should run:
- Choose how often you want the mover to run
- Choose when you want the mover to run
- Enable mover logging if you want to see what's being moved in the log files
- Click APPLY to save the settings
Mover Tuning - Plugin Settings
- This prevents the mover from running at the schedule set in Mover Settings. Setting this to "Yes" effectively disables the plugin schedule.
- Test Mode (dry run). Enable this the first few times to see what will happen without actually moving files.
-
We've had reports that enabling this option prevents the mover from moving certain files, so we recommend keeping it disabled.
Suggested:
Disabled -
Enable Mover Tuning logging.
- Enable or disable notifications for this plugin. Notifications appear in the Unraid GUI.
- Default save path for Mover Tuning log files. You can choose a different location if you prefer.
- Select how many days old log files (
*.log) and text files (*.txt) should be before Mover Tuning deletes them. - Select how many days old list files (
*.list) should be before Mover Tuning deletes them. -
Show advanced settings options. Make sure this is enabled.
Suggested:
Enabled -
If enabled, the mover will run during a parity check or rebuild.
- Set the priority for the mover process. Adjusting these options may help if other applications pause or buffer when the mover runs.
- Set the priority for the mover process. Adjusting these options may help if other applications pause or buffer when the mover runs.
-
Path to a script that runs before the mover starts. (This is where we add the path to the
mover-tuning-start.shscript.)Suggested:
/mnt/user/appdata/qbt-mover/mover-tuning-start.sh -
Path to a script that runs after the mover finishes. (This is where we add the path to the
mover-tuning-end.shscript.)Suggested:
/mnt/user/appdata/qbt-mover/mover-tuning-end.sh -
Select "Yes" to follow plugin filters, or "No" to run the original mover (ignores plugin filters) from the button.
Mover Tuning - Filters
-
When this percentage threshold is reached, the mover starts moving data off the cache pool.
If your cache drive is large enough, you could set this to 75%, for example. This lets you seed longer from your cache drive and have all upgrades happen there before moving to your array.
-
Set the percentage of disk space used on the cache drive after the mover completes. Setting this to 0% means the mover continues until all data is moved off the cache pool.
Setting this to 50%, for example, frees up space to 50% of your cache capacity and lets you seed longer from your cache drive.
-
Select whether you want to move files off the Primary (cache) based on their age in days.
Suggested:
Yes -
Select how many days old a file must be to move (up to 1 year). "Auto" moves from oldest to newest until the threshold is met.
Suggested:
Auto -
Show advanced filter options.
Suggested:
Yes -
Use CTIME (creation time) instead of MTIME (modification time) in the find command.
Suggested:
No -
Use ATIME (access time) instead of MTIME (modification time) in the find command.
Suggested:
No -
Select "Yes" if you want to move files based on their size in MB.
Suggested:
No -
Ignore all hidden files and directories.
Suggested:
No -
Set to "Yes" if you want to move all files from your cache to the array when the percentage below is exceeded.
- Set the percentage of disk space used on the Primary pool that triggers a move of all files from your cache to your array.
Mover Tuning - Options
-
Forces unRAID to switch to turbo write mode (reconstruct write) when the mover runs.
Suggested:
Personal Preference -
Choose the tool for moving files with plugin filters. You can select between Rsync or Move (Unraid's built-in file-moving utility).
- Move - Use Unraid's built-in tool for moving, but continue using Rsync for syncing files.
- Rsync - Use Rsync for both moving and syncing files.
Suggested:
Personal Preference and what works best for you. Some people report that Move didn't work but Rsync did. -
This removes the parent folder only of moved files. "Top Folder" removes the top-level folder of moved files on a share, including all subfolders if they're empty.
Suggested:
Top Folder -
Show advanced options.
Suggested:
Yes -
Moves files from shares to their Primary and/or Secondary storage if they're spread across Unattended Storage (disks/pools not assigned as a share's Primary or Secondary). May move older files from Primary→Secondary or Secondary→Primary if allowed.
Suggested:
No -
Click APPLY to save the settings
Option 2: User Scripts
Install the following plugins:
- User Scripts
- Python 3 for unRAID (unRAID Plugin)
qBit-Mover Script
Download the latest qBit-Mover script HERE.
Big thanks to bobokun, the developer of qBit Manage, for creating this script with all the requested changes.
Save the Script to Your Preferred Location
Place the qBit-Mover script somewhere easy to access and remember.
Suggested locations:
/mnt/user/appdata/qbt-mover/mover.py/mnt/user/appdata/scripts/mover.py
Install the qbittorrent-api Module
The script needs the qbittorrent-api module to work, so you need to install it when your unRAID server starts or when the Array starts for the first time.
Choose one of the following three methods (select a tab) to install qbittorrent-api.
This method creates a Python virtual environment on your disk. You'll use this to run and store dependencies (qbittorrent-api) for this specific environment.
With this method, you only need to set this up once and it will remain after reboots (unlike the other methods).
First, choose a location for your new Python environment.
Info
In the next steps, you'll choose a location to store the script. Try to keep things organized.
Suggested locations:
/mnt/user/appdata/qbt-mover/.venv/mnt/user/appdata/scripts/.venv
Run the following command in unRAID's terminal using the directory you chose:
python3 -m venv --clear /mnt/user/appdata/scripts/.venv
Now enter this new environment and install the dependency (qbittorrent-api):
source /mnt/user/appdata/scripts/.venv/bin/activate
pip3 install qbittorrent-api
deactivate # to exit the environment
Info
Replace /mnt/user/appdata/scripts/.venv with the path you chose.
This method installs the qbittorrent-api module when the Array starts for the first time.
In your unRAID Dashboard, go to the Settings tab and select User Scripts in the User Utilities section at the bottom.
At the bottom of the User Scripts page, click the ADD NEW SCRIPT button.
A popup will ask you to name the script. For this example, use Install qBittorrent-API and click OK.
Click the cogwheel next to the new script in the list and select Edit Script.
Copy and paste the following into the new window that opens, then click SAVE CHANGES.
#!/bin/bash
pip3 install qbittorrent-api
In the schedule list, select when the script should run and choose At First Array Start Only.
Click Apply.
Finally, click RUN IN BACKGROUND or restart your unRAID server to install the qbittorrent-api module.
This method installs the qbittorrent-api module when the unRAID server starts.
On your USB stick/key, go to /boot/config and open the go file with your text editor (VSCode or Notepad++).
Copy and paste the following command:
pip3 install qbittorrent-api
Restart your unRAID server or run the command above from the terminal.
Set Up the Scheduler
Set up the scheduler for when the mover should run.
In your unRAID Dashboard, go to the Settings tab and select User Scripts in the User Utilities section at the bottom.
At the bottom of the User Scripts page, click the ADD NEW SCRIPT button.
A popup will ask you to name the script. For this example, use qBittorrent Mover and click OK.
Click the cogwheel next to the new script in the list.
Choose your method (select a tab) and copy/paste the script into the new window that opens, then click SAVE CHANGES.
Important: Replace placeholders
Replace ip with your unRAID server IP and port with your qBittorrent WebGUI port.
#!/bin/bash
/usr/local/emhttp/plugins/dynamix/scripts/notify -s "qBittorrent Mover" -d "qBittorrent Mover starting @ `date +%H:%M:%S`."
echo "executing script to pause torrents and run mover."
python3 /mnt/user/appdata/scripts/mover.py --host "ip:port" --user "your_user" --password "your_password" --cache-mount "/mnt/cache" --days_from 0 --days_to 2
echo "qbittorrent-mover completed and resumed all paused torrents."
/usr/local/emhttp/plugins/dynamix/scripts/notify -s "qBittorrent Mover" -d "qBittorrent Mover completed @ `date +%H:%M:%S`."
#!/bin/bash
/usr/local/emhttp/plugins/dynamix/scripts/notify -s "qBittorrent Mover" -d "qBittorrent Mover starting @ `date +%H:%M:%S`."
echo "executing script to pause torrents and run mover."
/mnt/user/appdata/scripts/.venv/bin/python3 /mnt/user/appdata/scripts/mover.py --host "ip:port" --user "your_user" --password "your_password" --cache-mount "/mnt/cache" --days_from 0 --days_to 2
echo "qbittorrent-mover completed and resumed all paused torrents."
/usr/local/emhttp/plugins/dynamix/scripts/notify -s "qBittorrent Mover" -d "qBittorrent Mover completed @ `date +%H:%M:%S`."
Update the script path
Replace /mnt/user/appdata/scripts/ in the script with the path you chose for the Python script (qBit-Mover script).
Script Parameters Explained
| Parameter | Description |
|---|---|
--days_from |
Set the number of days to stop torrents from for the move |
--days_to |
Set the number of days to stop torrents to for the move |
--host |
Replace ip with your unRAID server IP and port with your qBittorrent WebGUI port |
--user |
Your qBittorrent username (if you have authentication enabled) |
--password |
Your qBittorrent password (if you have authentication enabled) |
--cache-mount |
Cache mount point in Unraid. This filters for only torrents that exist on the cache mount. Use this option ONLY if you follow the TRaSH Guides folder structure. (For the default cache drive, set this to /mnt/cache) |
Set the Schedule
Click the schedule dropdown to choose when the script should run. Select Custom.
After changing to Custom, you'll see an extra text field on the right where you can set your schedule using cron syntax.
For this example, we'll run the script every day at 4 AM:
0 4 * * *
You can create your schedule using crontab guru.
Questions or Suggestions?












