initial commit
This commit is contained in:
		
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
								
							| @@ -0,0 +1,49 @@ | ||||
| # Torrent Mover | ||||
|  | ||||
| A automated torrent management system for handling completed downloads and seeding management. | ||||
|  | ||||
| ## Features | ||||
| - Automatic file organization by category (Movies, Games, Apps, Books) | ||||
| - Seeding management based on ratio/time | ||||
| - Archive extraction support (RAR, ZIP, 7z) | ||||
| - Parallel processing | ||||
| - Checksum verification | ||||
| - Storage capacity monitoring | ||||
|  | ||||
| ## Installation | ||||
| ```bash | ||||
| sudo ./install.sh | ||||
|  | ||||
|  | ||||
|  | ||||
| torrent-mover [options] | ||||
| Options: | ||||
|   --dry-run       Simulate operations | ||||
|   --interactive   Confirm before processing | ||||
|   --cache-warmup  Pre-generate checksums | ||||
|  | ||||
|  | ||||
|  | ||||
| Git Hosting | ||||
| Repository URL: http://192.168.0.236:3000/masterdraco/torrent | ||||
|  | ||||
| Security | ||||
| Configuration files stored in /etc/torrent | ||||
|  | ||||
| Processed logs in /var/log/torrent_* | ||||
|  | ||||
| Copy | ||||
|  | ||||
| 3. **Repository Structure**: | ||||
| ```bash | ||||
| . | ||||
| ├── etc | ||||
| │   └── torrent | ||||
| │       └── mover.conf | ||||
| ├── usr | ||||
| │   └── local | ||||
| │       └── bin | ||||
| │           └── torrent-mover | ||||
| ├── install.sh | ||||
| └── README.md | ||||
|  | ||||
|   | ||||
							
								
								
									
										31
									
								
								etc/torrent/mover.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								etc/torrent/mover.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| # Transmission credentials | ||||
| TRANSMISSION_IP="192.168.5.19" | ||||
| TRANSMISSION_PORT="9091" | ||||
| TRANSMISSION_USER="" | ||||
| TRANSMISSION_PASSWORD="" | ||||
|  | ||||
| # Seeding criteria | ||||
| SEED_RATIO="2.5" | ||||
| SEED_TIME="2880"  # Minutes | ||||
|  | ||||
| # Directory mappings | ||||
| DIR_GAMES_DST="/mnt/dsnas1/Games" | ||||
| DIR_APPS_DST="/mnt/dsnas1/Apps" | ||||
| DIR_MOVIES_DST="/mnt/dsnas1/Movies" | ||||
| DIR_BOOKS_DST="/mnt/dsnas1/Books" | ||||
| DEFAULT_DST="/mnt/dsnas1/Other" | ||||
|  | ||||
| # Storage directories (comma-separated) | ||||
| STORAGE_DIRS="/mnt/dsnas/Movies" | ||||
|  | ||||
| # Performance settings | ||||
| PARALLEL_THREADS="16"  # Match CPU core count | ||||
| PARALLEL_PROCESSING=1 | ||||
|  | ||||
| # Operation mode | ||||
| COPY_MODE="copy"  # move|copy | ||||
| PROCESSED_LOG="/var/log/torrent_processed.log" | ||||
| CHECKSUM_DB="/var/lib/torrent/checksums.db" | ||||
|  | ||||
| # System settings | ||||
| LOG_FILE="/var/log/torrent_mover.log" | ||||
							
								
								
									
										70
									
								
								install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								install.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| #!/bin/bash | ||||
| set -e | ||||
|  | ||||
| # Git repository configuration | ||||
| GIT_REPO="http://192.168.0.236:3000/masterdraco/torrent" | ||||
| REPO_CREDENTIALS="masterdraco:mlvfnj78" | ||||
|  | ||||
| # Check root privileges | ||||
| if [ "$EUID" -ne 0 ]; then | ||||
|     echo "Please run as root" | ||||
|     exit 1 | ||||
| fi | ||||
|  | ||||
| # Install dependencies | ||||
| echo "Checking dependencies..." | ||||
| declare -A PKGS=( | ||||
|     [transmission-cli]="transmission-remote" | ||||
|     [unrar]="unrar" | ||||
|     [unzip]="unzip" | ||||
|     [p7zip-full]="7z" | ||||
|     [parallel]="parallel" | ||||
|     [bc]="bc" | ||||
|     [git]="git" | ||||
| ) | ||||
|  | ||||
| for pkg in "${!PKGS[@]}"; do | ||||
|     if ! command -v "${PKGS[$pkg]}" &> /dev/null; then | ||||
|         echo "Installing $pkg..." | ||||
|         apt-get update | ||||
|         apt-get install -y "$pkg" | ||||
|     fi | ||||
| done | ||||
|  | ||||
| # Create directory structure | ||||
| echo "Creating directory structure..." | ||||
| mkdir -p /etc/torrent | ||||
| mkdir -p /usr/local/bin | ||||
|  | ||||
| # Install files | ||||
| echo "Installing files..." | ||||
| cp -v etc/torrent/mover.conf /etc/torrent/ | ||||
| cp -v usr/local/bin/torrent-mover /usr/local/bin/ | ||||
| chmod +x /usr/local/bin/torrent-mover | ||||
|  | ||||
| # Set permissions | ||||
| echo "Setting permissions..." | ||||
| chmod 600 /etc/torrent/mover.conf | ||||
| chown root:root /etc/torrent/mover.conf | ||||
|  | ||||
| # Initialize Git repository | ||||
| if [ ! -d .git ]; then | ||||
|     echo "Initializing Git repository..." | ||||
|     git init | ||||
|     git config user.name "torrent-mover-installer" | ||||
|     git config user.email "installer@localhost" | ||||
|     git remote add origin "http://$REPO_CREDENTIALS@192.168.0.236:3000/masterdraco/torrent.git" | ||||
| fi | ||||
|  | ||||
| # Add files to Git | ||||
| git add . | ||||
| git commit -m "Initial installation commit" || true | ||||
|  | ||||
| # Push to remote repository | ||||
| echo "Pushing to Git repository..." | ||||
| git push -u origin master | ||||
|  | ||||
| echo "" | ||||
| echo "Installation complete!" | ||||
| echo "Configuration file: /etc/torrent/mover.conf" | ||||
| echo "Main script: /usr/local/bin/torrent-mover" | ||||
							
								
								
									
										300
									
								
								usr/local/bin/torrent-mover
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										300
									
								
								usr/local/bin/torrent-mover
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,300 @@ | ||||
| #!/bin/bash | ||||
| # Torrent Mover v5.1 - Numeric Handling Fix | ||||
|  | ||||
| set -o errexit | ||||
| set -o nounset | ||||
| set -o pipefail | ||||
|  | ||||
| # Load configuration | ||||
| CONFIG_FILE="/etc/torrent/mover.conf" | ||||
| source "${CONFIG_FILE}" | ||||
|  | ||||
| # Runtime flags | ||||
| DRY_RUN=0 | ||||
| INTERACTIVE=0 | ||||
| CACHE_WARMUP=0 | ||||
|  | ||||
| # Color codes | ||||
| RED='\033[0;31m' | ||||
| GREEN='\033[0;32m' | ||||
| YELLOW='\033[1;33m' | ||||
| NC='\033[0m' | ||||
|  | ||||
| # Initialize storage directories | ||||
| STORAGE_DIRS_ARRAY=() | ||||
| if [[ -n "${STORAGE_DIRS}" ]]; then | ||||
|     IFS=',' read -ra STORAGE_DIRS_ARRAY <<< "${STORAGE_DIRS}" | ||||
| fi | ||||
|  | ||||
| # Logging functions | ||||
| log_info() { echo -e "${GREEN}[INFO]${NC} $(date '+%F %T') - $*" | tee -a "${LOG_FILE}"; } | ||||
| log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%F %T') - $*" | tee -a "${LOG_FILE}"; } | ||||
| log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%F %T') - $*" | tee -a "${LOG_FILE}"; } | ||||
|  | ||||
| # Dependency check | ||||
| check_dependencies() { | ||||
|     local deps=("transmission-remote" "unrar" "unzip" "7z" "parallel" "bc") | ||||
|     for dep in "${deps[@]}"; do | ||||
|         if ! command -v "${dep}" >/dev/null 2>&1; then | ||||
|             log_error "Missing dependency: ${dep}" | ||||
|             exit 1 | ||||
|         fi | ||||
|     done | ||||
| } | ||||
|  | ||||
| # Disk usage monitoring | ||||
| declare -A CHECKED_MOUNTS=() | ||||
|  | ||||
| check_disk_usage() { | ||||
|     local dir="$1" | ||||
|     [[ -z "${dir}" ]] && return | ||||
|      | ||||
|     if ! df -P "${dir}" &>/dev/null; then | ||||
|         log_warn "Directory not found: ${dir}" | ||||
|         return | ||||
|     fi | ||||
|      | ||||
|     local mount_point=$(df -P "${dir}" | awk 'NR==2 {print $6}') | ||||
|     [[ -z "${mount_point}" ]] && return | ||||
|  | ||||
|     if [[ -z "${CHECKED_MOUNTS["${mount_point}"]+x}" ]]; then | ||||
|         local usage=$(df -P "${dir}" | awk 'NR==2 {sub(/%/, "", $5); print $5}') | ||||
|         if (( usage >= 90 )); then | ||||
|             log_warn "Storage warning: ${mount_point} at ${usage}% capacity" | ||||
|         fi | ||||
|         CHECKED_MOUNTS["${mount_point}"]=1 | ||||
|     fi | ||||
| } | ||||
|  | ||||
| # Checksum database | ||||
| init_checksum_db() { | ||||
|     mkdir -p "$(dirname "${CHECKSUM_DB}")" | ||||
|     touch "${CHECKSUM_DB}" | ||||
|     chmod 600 "${CHECKSUM_DB}" | ||||
| } | ||||
|  | ||||
| record_checksums() { | ||||
|     log_info "Generating checksums with ${PARALLEL_THREADS:-$(nproc)} threads" | ||||
|     find "$@" -type f \( -iname "*.nfo" -o -iname "*.sfv" \) -prune -o -type f -print0 | \ | ||||
|         parallel -0 -j ${PARALLEL_THREADS:-$(nproc)} md5sum | \ | ||||
|         sort > "${CHECKSUM_DB}.tmp" | ||||
|     mv "${CHECKSUM_DB}.tmp" "${CHECKSUM_DB}" | ||||
| } | ||||
|  | ||||
| file_metadata() { | ||||
|     find "$1" -type f \( -iname "*.nfo" -o -iname "*.sfv" \) -prune -o -type f -printf "%s %T@ %p\n" | \ | ||||
|         sort | \ | ||||
|         md5sum | \ | ||||
|         awk '{print $1}' | ||||
| } | ||||
|  | ||||
| files_need_processing() { | ||||
|     local src="$1" shift | ||||
|     local targets=("$@") | ||||
|      | ||||
|     [[ ! -d "${src}" ]] && return 1 | ||||
|  | ||||
|     local src_meta=$(file_metadata "${src}") | ||||
|      | ||||
|     for target in "${targets[@]}"; do | ||||
|         [[ ! -d "${target}" ]] && continue | ||||
|         local target_meta=$(file_metadata "${target}") | ||||
|         [[ "${src_meta}" == "${target_meta}" ]] && return 1 | ||||
|     done | ||||
|  | ||||
|     local src_checksums=$(find "${src}" -type f \( -iname "*.nfo" -o -iname "*.sfv" \) -prune -o -type f -exec md5sum {} \; | sort) | ||||
|      | ||||
|     for target in "${targets[@]}"; do | ||||
|         [[ ! -d "${target}" ]] && continue | ||||
|         local target_checksums=$(find "${target}" -type f \( -iname "*.nfo" -o -iname "*.sfv" \) -prune -o -type f -exec md5sum {} \; | sort) | ||||
|         diff <(echo "${src_checksums}") <(echo "${target_checksums}") >/dev/null && return 1 | ||||
|     done | ||||
|      | ||||
|     return 0 | ||||
| } | ||||
|  | ||||
| warm_cache() { | ||||
|     log_info "Starting cache warmup for Movies..." | ||||
|     local targets=("${DIR_MOVIES_DST}" "${STORAGE_DIRS_ARRAY[@]}") | ||||
|     record_checksums "${targets[@]}" | ||||
|     log_info "Cache warmup completed. Checksums stored in ${CHECKSUM_DB}" | ||||
| } | ||||
|  | ||||
| is_processed() { | ||||
|     grep -q "^${1}$" "${PROCESSED_LOG}" 2>/dev/null | ||||
| } | ||||
|  | ||||
| mark_processed() { | ||||
|     echo "${1}" >> "${PROCESSED_LOG}" | ||||
| } | ||||
|  | ||||
| get_destination() { | ||||
|     case "${1}" in | ||||
|         *Games*)  echo "${DIR_GAMES_DST}";; | ||||
|         *Apps*)   echo "${DIR_APPS_DST}";; | ||||
|         *Movies*) echo "${DIR_MOVIES_DST}";; | ||||
|         *Books*)  echo "${DIR_BOOKS_DST}";; | ||||
|         *)        echo "${DEFAULT_DST}";; | ||||
|     esac | ||||
| } | ||||
|  | ||||
| handle_archives() { | ||||
|     local src="$1" dst="$2" | ||||
|     find "${src}" -type f \( -iname "*.rar" -o -iname "*.zip" -o -iname "*.7z" \) | \ | ||||
|     while read -r arch; do | ||||
|         log_info "Extracting ${arch##*/}" | ||||
|         case "${arch##*.}" in | ||||
|             rar) unrar x -o- "${arch}" "${dst}";; | ||||
|             zip) unzip -o "${arch}" -d "${dst}";; | ||||
|             7z) 7z x "${arch}" -o"${dst}";; | ||||
|         esac | ||||
|     done | ||||
| } | ||||
|  | ||||
| move_files() { | ||||
|     if (( PARALLEL_PROCESSING )); then | ||||
|         parallel -j ${PARALLEL_THREADS:-$(nproc)} mv {} "${1}" ::: "${2}"/* | ||||
|     else | ||||
|         mv "${2}"/* "${1}" | ||||
|     fi | ||||
| } | ||||
|  | ||||
| copy_files() { | ||||
|     if (( PARALLEL_PROCESSING )); then | ||||
|         parallel -j ${PARALLEL_THREADS:-$(nproc)} cp -r {} "${1}" ::: "${2}"/* | ||||
|     else | ||||
|         cp -r "${2}"/* "${1}" | ||||
|     fi | ||||
| } | ||||
|  | ||||
| process_copy() { | ||||
|     local id="$1" hash="$2" src="$3" dst="$4" | ||||
|      | ||||
|     if (( DRY_RUN )); then | ||||
|         log_info "[DRY RUN] Would process torrent ${id}:" | ||||
|         log_info "  - Copy files from ${src} to ${dst}" | ||||
|         return | ||||
|     fi | ||||
|  | ||||
|     mkdir -p "${dst}" | ||||
|     handle_archives "${src}" "${dst}" | ||||
|      | ||||
|     case "${COPY_MODE}" in | ||||
|         move) move_files "${dst}" "${src}";; | ||||
|         copy) copy_files "${dst}" "${src}";; | ||||
|     esac | ||||
| } | ||||
|  | ||||
| process_removal() { | ||||
|     local id="$1" | ||||
|      | ||||
|     if (( DRY_RUN )); then | ||||
|         log_info "[DRY RUN] Would remove torrent ${id}" | ||||
|         return | ||||
|     fi | ||||
|  | ||||
|     transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" \ | ||||
|         -n "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" \ | ||||
|         -t "${id}" --remove-and-delete | ||||
| } | ||||
|  | ||||
| main() { | ||||
|     check_dependencies | ||||
|     init_checksum_db | ||||
|  | ||||
|     if (( CACHE_WARMUP )); then | ||||
|         warm_cache | ||||
|         exit 0 | ||||
|     fi | ||||
|  | ||||
|     log_info "Starting processing" | ||||
|     declare -A warned_dirs=() | ||||
|  | ||||
|     transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" \ | ||||
|         -n "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" -l | \ | ||||
|         awk 'NR>1 && $1 ~ /^[0-9]+$/ {print $1}' | \ | ||||
|         while read -r id; do | ||||
|              | ||||
|             local info=$(transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" \ | ||||
|                 -n "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" -t "${id}" -i) | ||||
|             local hash=$(grep "Hash:" <<< "${info}" | awk '{print $2}') | ||||
|              | ||||
|             # Sanitize numeric values with fallbacks | ||||
|             local ratio=$(grep "Ratio:" <<< "${info}" | awk '{print $2 == "None" ? 0 : $2}' | tr -cd '0-9.') | ||||
|             ratio=${ratio:-0}  # Handle empty values | ||||
|              | ||||
|             local time=$(grep "Seeding Time:" <<< "${info}" | awk '{print $3 == "None" ? 0 : $3}' | tr -cd '0-9.') | ||||
|             time=${time:-0}  # Handle empty values | ||||
|              | ||||
|             local percent_done=$(grep "Percent Done:" <<< "${info}" | awk '{gsub(/%/, ""); print $3 == "None" ? 0 : $3}') | ||||
|             percent_done=${percent_done:-0}  # Handle empty values | ||||
|              | ||||
|             local dir=$(grep "Location:" <<< "${info}" | cut -d' ' -f4-) | ||||
|             local dst=$(get_destination "${dir}") | ||||
|  | ||||
|             # Initialize warning tracking | ||||
|             [[ -z "${warned_dirs["${dir}"]+x}" ]] && warned_dirs["${dir}"]=0 | ||||
|  | ||||
|             # 1. Handle completed downloads | ||||
|             if (( $(bc <<< "${percent_done} >= 100") )) && ! is_processed "${hash}"; then | ||||
|                 log_info "Processing completed torrent ${id} (${percent_done}% done)" | ||||
|                  | ||||
|                 if [[ "${dst}" == "${DEFAULT_DST}" ]] && (( warned_dirs["${dir}"] == 0 )); then | ||||
|                     log_warn "Using default destination for: ${dir}" | ||||
|                     warned_dirs["${dir}"]=1 | ||||
|                 fi | ||||
|  | ||||
|                 # Determine check targets | ||||
|                 local targets=("${dst}") | ||||
|                 if [[ "${dst}" == "${DIR_MOVIES_DST}" ]]; then | ||||
|                     targets+=("${STORAGE_DIRS_ARRAY[@]}") | ||||
|                 fi | ||||
|  | ||||
|                 if ! files_need_processing "${dir}" "${targets[@]}"; then | ||||
|                     log_info "Skipping copy - files already exist in:" | ||||
|                     for target in "${targets[@]}"; do | ||||
|                         [[ -d "${target}" ]] && log_info "    - ${target}" | ||||
|                     done | ||||
|                 else | ||||
|                     process_copy "${id}" "${hash}" "${dir}" "${dst}" | ||||
|                 fi | ||||
|                  | ||||
|                 mark_processed "${hash}" | ||||
|             fi | ||||
|  | ||||
|             # 2. Handle seeding criteria | ||||
|             if (( $(bc <<< "${ratio} >= ${SEED_RATIO}") )) || (( $(bc <<< "${time} >= ${SEED_TIME}") )); then | ||||
|                 log_info "Removing torrent ${id} (Ratio: ${ratio}, Time: ${time})" | ||||
|                 process_removal "${id}" | ||||
|             fi | ||||
|         done | ||||
|  | ||||
|     # Final disk checks | ||||
|     check_disk_usage "${DIR_GAMES_DST}" | ||||
|     check_disk_usage "${DIR_APPS_DST}" | ||||
|     check_disk_usage "${DIR_MOVIES_DST}" | ||||
|     check_disk_usage "${DIR_BOOKS_DST}" | ||||
|     check_disk_usage "${DEFAULT_DST}" | ||||
| } | ||||
|  | ||||
| # Argument handling | ||||
| while [[ $# -gt 0 ]]; do | ||||
|     case "$1" in | ||||
|         --dry-run) DRY_RUN=1; shift ;; | ||||
|         --interactive) INTERACTIVE=1; shift ;; | ||||
|         --cache-warmup) CACHE_WARMUP=1; shift ;; | ||||
|         --help) | ||||
|             echo "Usage: $0 [--dry-run] [--interactive] [--cache-warmup]" | ||||
|             exit 0 | ||||
|             ;; | ||||
|         *) echo "Invalid option: $1"; exit 1 ;; | ||||
|     esac | ||||
| done | ||||
|  | ||||
| # Execution | ||||
| if (( INTERACTIVE )); then | ||||
|     read -rp "Confirm processing? (y/n) " choice | ||||
|     [[ "${choice}" =~ ^[Yy]$ ]] || exit 0 | ||||
| fi | ||||
|  | ||||
| main | ||||
		Reference in New Issue
	
	Block a user
	 root
					root