diff --git a/docs/Downloaders/qBittorrent/Tips/How-to-run-the-unRaid-mover-for-qBittorrent.md b/docs/Downloaders/qBittorrent/Tips/How-to-run-the-unRaid-mover-for-qBittorrent.md index 5e1daa7b0f..3749aaeaa6 100644 --- a/docs/Downloaders/qBittorrent/Tips/How-to-run-the-unRaid-mover-for-qBittorrent.md +++ b/docs/Downloaders/qBittorrent/Tips/How-to-run-the-unRaid-mover-for-qBittorrent.md @@ -125,10 +125,10 @@ chmod +x /mnt/user/appdata/qbt-mover/mover-tuning-end.sh After editing the scripts, make sure they use LF line endings (Unix format). If you edited the files on Windows, they may have CRLF line endings, which can cause errors. -Run the following command to convert all `.sh` files to the correct format: +Run the following command to convert all `.sh` and `.cfg` files to the correct format: ```bash -for file in *.sh; do [ -f "$file" ] && sed -i 's/\r$//' "$file" && echo "Converted $file"; done +for file in *.sh *.cfg; do [ -f "$file" ] && sed -i 's/\r$//' "$file" && echo "Converted $file"; done ``` **How to use this command:** @@ -143,7 +143,7 @@ for file in *.sh; do [ -f "$file" ] && sed -i 's/\r$//' "$file" && echo "Convert 3. Paste the command above 4. Press ++enter++ -This converts all `.sh` files from CRLF (Windows line endings) to LF (Unix line endings) in the current directory. +This converts all `.sh` and `.cfg` files from CRLF (Windows line endings) to LF (Unix line endings) in the current directory. --- diff --git a/includes/downloaders/mover-tuning-end.sh b/includes/downloaders/mover-tuning-end.sh index 096ef62533..310bf5d7fa 100644 --- a/includes/downloaders/mover-tuning-end.sh +++ b/includes/downloaders/mover-tuning-end.sh @@ -3,12 +3,12 @@ set -euo pipefail # Exit on error, undefined variables, and pipe failures # ===================================== # Script: qBittorrent Cache Mover - End -# Version: 1.0.0 -# Updated: 20251121 +# Version: 1.1.0 +# Updated: 20251201 # ===================================== # Script version and update check URLs -readonly SCRIPT_VERSION="1.0.0" +readonly SCRIPT_VERSION="1.1.0" readonly SCRIPT_RAW_URL="https://raw.githubusercontent.com/TRaSH-Guides/Guides/refs/heads/master/includes/downloaders/mover-tuning-end.sh" # Get the directory where the script is located @@ -44,6 +44,63 @@ notify() { fi } +# ================================ +# CONFIG FORMAT DETECTION +# ================================ +detect_config_format() { + # Check if array-based config is used + if [[ -v HOSTS[@] ]] && [[ ${#HOSTS[@]} -gt 0 ]]; then + echo "array" + else + echo "legacy" + fi +} + +get_instance_count() { + local format + format=$(detect_config_format) + + if [[ "$format" == "array" ]]; then + echo "${#HOSTS[@]}" + else + # Legacy format: count based on ENABLE_QBIT_2 + if [[ "${ENABLE_QBIT_2:-false}" == true ]]; then + echo "2" + else + echo "1" + fi + fi +} + +get_instance_details() { + local index="$1" + local format + format=$(detect_config_format) + + if [[ "$format" == "array" ]]; then + # Array format: set global variables + INSTANCE_NAME="${NAMES[$index]:-qBit-Instance-$((index + 1))}" + INSTANCE_HOST="${HOSTS[$index]}" + INSTANCE_USER="${USERS[$index]}" + INSTANCE_PASSWORD="${PASSWORDS[$index]}" + else + # Legacy format: map index to old variables + if [[ $index -eq 0 ]]; then + INSTANCE_NAME="${QBIT_NAME_1}" + INSTANCE_HOST="${QBIT_HOST_1}" + INSTANCE_USER="${QBIT_USER_1}" + INSTANCE_PASSWORD="${QBIT_PASS_1}" + elif [[ $index -eq 1 ]]; then + INSTANCE_NAME="${QBIT_NAME_2}" + INSTANCE_HOST="${QBIT_HOST_2}" + INSTANCE_USER="${QBIT_USER_2}" + INSTANCE_PASSWORD="${QBIT_PASS_2}" + else + error "Invalid instance index: $index" + fi + fi +} + # ================================ # VERSION CHECK FUNCTION # ================================ @@ -91,9 +148,17 @@ check_script_version() { # Compare versions if [[ "$SCRIPT_VERSION" != "$latest_version" ]]; then # Simple version comparison (works for semantic versioning) - if printf '%s\n' "$latest_version" "$SCRIPT_VERSION" | sort -V | head -n1 | grep -q "^$SCRIPT_VERSION$" 2>/dev/null || true; then + # Sort both versions and check if SCRIPT_VERSION comes first (is older) + local oldest_version + oldest_version=$(printf '%s\n' "$latest_version" "$SCRIPT_VERSION" | sort -V | head -n1) + + if [[ "$oldest_version" == "$SCRIPT_VERSION" ]]; then + # SCRIPT_VERSION is older, so there's a newer version available log "⚠ New version available: $latest_version" notify "mover-tuning-end.sh Update" "Version $latest_version available (current: $SCRIPT_VERSION)

📖 Visit the TRaSH-Guides for the latest version" + else + # latest_version is older, local version is newer + log "✓ Local version ($SCRIPT_VERSION) is newer than remote ($latest_version)" fi else log "✓ Script is up to date" @@ -297,6 +362,31 @@ validate_config() { [[ -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 instance configuration + local format + format=$(detect_config_format) + + if [[ "$format" == "array" ]]; then + # Validate array-based config + [[ ${#HOSTS[@]} -gt 0 ]] || error "HOSTS array is empty" + [[ ${#USERS[@]} -eq ${#HOSTS[@]} ]] || error "USERS array length doesn't match HOSTS" + [[ ${#PASSWORDS[@]} -eq ${#HOSTS[@]} ]] || error "PASSWORDS array length doesn't match HOSTS" + + # NAMES array is optional, but if present should match + if [[ -v NAMES[@] ]] && [[ ${#NAMES[@]} -gt 0 ]]; then + [[ ${#NAMES[@]} -eq ${#HOSTS[@]} ]] || error "NAMES array length doesn't match HOSTS" + fi + + log "✓ Using array-based configuration (${#HOSTS[@]} instance(s))" + else + # Validate legacy config + [[ -n "${QBIT_HOST_1:-}" ]] || error "QBIT_HOST_1 is not set" + [[ -n "${QBIT_USER_1:-}" ]] || error "QBIT_USER_1 is not set" + [[ -n "${QBIT_PASS_1:-}" ]] || error "QBIT_PASS_1 is not set" + + log "✓ Using legacy configuration" + fi + # Validate duplicate finder if enabled if [[ "$ENABLE_DUPLICATE_FINDER" == true ]]; then if [[ "$ENABLE_AUTO_INSTALLER" == true ]]; then @@ -372,18 +462,15 @@ main() { # 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 all instances + local instance_count + instance_count=$(get_instance_count) - # 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 + for ((i=0; i/dev/null || log "⚠ Warning: Could not set ownership for $1" } +# ================================ +# CONFIG FORMAT DETECTION +# ================================ +detect_config_format() { + # Check if array-based config is used + if [[ -v HOSTS[@] ]] && [[ ${#HOSTS[@]} -gt 0 ]]; then + echo "array" + else + echo "legacy" + fi +} + +get_instance_count() { + local format + format=$(detect_config_format) + + if [[ "$format" == "array" ]]; then + echo "${#HOSTS[@]}" + else + # Legacy format: count based on ENABLE_QBIT_2 + if [[ "${ENABLE_QBIT_2:-false}" == true ]]; then + echo "2" + else + echo "1" + fi + fi +} + +get_instance_details() { + local index="$1" + local format + format=$(detect_config_format) + + if [[ "$format" == "array" ]]; then + # Array format: set global variables + INSTANCE_NAME="${NAMES[$index]:-qBit-Instance-$((index + 1))}" + INSTANCE_HOST="${HOSTS[$index]}" + INSTANCE_USER="${USERS[$index]}" + INSTANCE_PASSWORD="${PASSWORDS[$index]}" + else + # Legacy format: map index to old variables + if [[ $index -eq 0 ]]; then + INSTANCE_NAME="${QBIT_NAME_1}" + INSTANCE_HOST="${QBIT_HOST_1}" + INSTANCE_USER="${QBIT_USER_1}" + INSTANCE_PASSWORD="${QBIT_PASS_1}" + elif [[ $index -eq 1 ]]; then + INSTANCE_NAME="${QBIT_NAME_2}" + INSTANCE_HOST="${QBIT_HOST_2}" + INSTANCE_USER="${QBIT_USER_2}" + INSTANCE_PASSWORD="${QBIT_PASS_2}" + else + error "Invalid instance index: $index" + fi + fi +} + # ================================ # VERSION CHECK FUNCTION # ================================ @@ -104,9 +161,17 @@ check_script_version() { # Compare versions if [[ "$SCRIPT_VERSION" != "$latest_version" ]]; then # Simple version comparison (works for semantic versioning) - if printf '%s\n' "$latest_version" "$SCRIPT_VERSION" | sort -V | head -n1 | grep -q "^$SCRIPT_VERSION$" 2>/dev/null || true; then + # Sort both versions and check if SCRIPT_VERSION comes first (is older) + local oldest_version + oldest_version=$(printf '%s\n' "$latest_version" "$SCRIPT_VERSION" | sort -V | head -n1) + + if [[ "$oldest_version" == "$SCRIPT_VERSION" ]]; then + # SCRIPT_VERSION is older, so there's a newer version available log "⚠ New version available: $latest_version" notify "mover-tuning-start.sh Update" "Version $latest_version available (current: $SCRIPT_VERSION)

📖 Visit the TRaSH-Guides for the latest version" + else + # latest_version is older, local version is newer + log "✓ Local version ($SCRIPT_VERSION) is newer than remote ($latest_version)" fi else log "✓ Script is up to date" @@ -162,9 +227,17 @@ check_config_version() { # Compare versions if [[ "$current_config_version" != "$remote_config_version" ]]; then # Simple version comparison (works for semantic versioning) - if printf '%s\n' "$remote_config_version" "$current_config_version" | sort -V | head -n1 | grep -q "^$current_config_version$" 2>/dev/null || true; then + # Sort both versions and check if current_config_version comes first (is older) + local oldest_version + oldest_version=$(printf '%s\n' "$remote_config_version" "$current_config_version" | sort -V | head -n1) + + if [[ "$oldest_version" == "$current_config_version" ]]; then + # current_config_version is older, so there's a newer version available log "⚠ New config version available: $remote_config_version" notify "mover-tuning.cfg Update" "Config version $remote_config_version available
Current version: $current_config_version

📖 Visit the TRaSH-Guides for the latest version" + else + # remote_config_version is older, local version is newer + log "✓ Local config version ($current_config_version) is newer than remote ($remote_config_version)" fi else log "✓ Config is up to date" @@ -272,6 +345,45 @@ validate_config() { [[ "$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" + + # Validate instance configuration + local format + format=$(detect_config_format) + + if [[ "$format" == "array" ]]; then + # Validate array-based config + if [[ ${#HOSTS[@]} -eq 0 ]]; then + notify "Configuration Error" "HOSTS array is empty" + error "HOSTS array is empty" + fi + + if [[ ${#USERS[@]} -ne ${#HOSTS[@]} ]]; then + notify "Configuration Error" "USERS array length (${#USERS[@]}) doesn't match HOSTS (${#HOSTS[@]})" + error "USERS array length doesn't match HOSTS" + fi + + if [[ ${#PASSWORDS[@]} -ne ${#HOSTS[@]} ]]; then + notify "Configuration Error" "PASSWORDS array length (${#PASSWORDS[@]}) doesn't match HOSTS (${#HOSTS[@]})" + error "PASSWORDS array length doesn't match HOSTS" + fi + + # NAMES array is optional, but if present should match + if [[ -v NAMES[@] ]] && [[ ${#NAMES[@]} -gt 0 ]]; then + if [[ ${#NAMES[@]} -ne ${#HOSTS[@]} ]]; then + notify "Configuration Error" "NAMES array length (${#NAMES[@]}) doesn't match HOSTS (${#HOSTS[@]})" + error "NAMES array length doesn't match HOSTS" + fi + fi + + log "✓ Using array-based configuration (${#HOSTS[@]} instance(s))" + else + # Validate legacy config + [[ -n "${QBIT_HOST_1:-}" ]] || error "QBIT_HOST_1 is not set" + [[ -n "${QBIT_USER_1:-}" ]] || error "QBIT_USER_1 is not set" + [[ -n "${QBIT_PASS_1:-}" ]] || error "QBIT_PASS_1 is not set" + + log "✓ Using legacy configuration" + fi } # ================================ @@ -350,12 +462,15 @@ main() { fi fi - # Process instances - process_qbit_instance "$QBIT_NAME_1" "$QBIT_HOST_1" "$QBIT_USER_1" "$QBIT_PASS_1" || ((failed_instances++)) + # Process all instances + local instance_count + instance_count=$(get_instance_count) - if [[ "$ENABLE_QBIT_2" == true ]]; then - process_qbit_instance "$QBIT_NAME_2" "$QBIT_HOST_2" "$QBIT_USER_2" "$QBIT_PASS_2" || ((failed_instances++)) - fi + for ((i=0; i # ============================================= -readonly CONFIG_VERSION="1.0.0" # ONLY UPDATE THE VERSION NUMBER WHEN A CONFIGURATION FILE CHANGE HAS BEEN MADE +readonly CONFIG_VERSION="1.1.0" # ONLY UPDATE THE VERSION NUMBER WHEN A CONFIGURATION FILE CHANGE HAS BEEN MADE readonly ENABLE_VERSION_CHECK=true # Set to false to disable all update checks and notifications (not recommended) # ============================================= @@ -15,22 +15,18 @@ readonly ENABLE_AUTO_INSTALLER=true # Set to false to disable qBit-Api and qBit # 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 DAYS_TO=99 # Maximum age (days) for torrent selection (must be >= DAYS_FROM) 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 +# qBittorrent instances +# Supports unlimited qBittorrent instances +# Arrays NAMES, HOSTS, USERS and PASSWORDS must all have the same length +# NAMES array is optional - if omitted, instances will be named "qBit-Instance-1", "qBit-Instance-2", etc. +readonly NAMES=("qBit-Movies" "qBit-TV" "qBit-Music") # qBittorrent instance names +readonly HOSTS=("192.168.2.200:8088" "192.168.2.200:8811" "192.168.2.200:8822") # qBittorrent host:port +readonly USERS=("admin" "admin" "admin") # qBittorrent usernames +readonly PASSWORDS=("qbt1-password" "qbt2-password" "qbt3-password") # qBittorrent passwords # qBit-Manage integration (OPTIONAL) readonly ENABLE_QBIT_MANAGE=true # Set to false to disable qBit-Manage