#!/bin/bash #Copyright 2005-2024, Lime Technology, Inc. #License: GPLv2 only # This is the 'mover' script used for moving files between pools and/or the main unRAID array. # It is typically invoked via cron. # The script is set up so that hidden directories (i.e., directory names beginning with a '.' # character) at the topmost level of a pool or an array disk are not moved. This behavior # can be turned off by uncommenting the following line: # shopt -s dotglob # Files at the top level of a pool or an array disk are never moved. # The 'find' command generates a list of all files and directories of a share. # For each file, if the file is not "in use" by any process (as detected by 'fuser' command) # and not attached to any loop device (as detected by 'losetup' command), # then the file is moved, and upon success, deleted from the source disk. If the file already # exists on the target, it is not moved and the source is not deleted. All meta-data of moved # files/directories is preserved: permissions, ownership, extended attributes, and access/modified # timestamps. # If an error occurs in copying a file, the partial file, if present, is deleted and the # operation continues on to the next file. PIDFILE="/var/run/mover.pid" CFGPATH="/boot/config/shares" DEBUGGING="" move() { find "$1" -depth 2>/dev/null | /usr/libexec/unraid/move $DEBUGGING # second pass to clean up leftover empty directories find "$1" -depth -type d 2>/dev/null | /usr/libexec/unraid/move $DEBUGGING } start() { if [ -f $PIDFILE ]; then if ps h $(cat $PIDFILE) | grep mover ; then echo "mover: already running" >&2 exit 1 fi fi echo $$ >/var/run/mover.pid echo "mover: started" >&2 shopt -s nullglob for SHARECFG in $CFGPATH/*.cfg ; do SHARE=$(basename "$SHARECFG" .cfg) source <(fromdos < "$SHARECFG") # maybe move from primary to secondary if [[ $shareUseCache = yes && -n $shareCachePool && -d "/mnt/$shareCachePool/$SHARE" ]]; then if [[ -n $shareCachePool2 ]]; then # secondary is a pool if [[ -d "/mnt/$shareCachePool2" ]]; then move "/mnt/$shareCachePool/$SHARE" fi else # secondary is the unRAID array, ensure at least one data disk is present disks=(/mnt/disk[1-9]/ /mnt/disk[1-9][0-9]/) if [[ ${#disks[@]} -gt 0 ]]; then move "/mnt/$shareCachePool/$SHARE" fi fi fi # maybe move from secondary to primary if [[ $shareUseCache = prefer ]]; then if [[ -n $shareCachePool2 ]]; then # secondary is a pool (primary is also a pool) if [[ -d "/mnt/$shareCachePool2/$SHARE" && -n $shareCachePool && -d "/mnt/$shareCachePool" ]]; then move "/mnt/$shareCachePool2/$SHARE" fi else # secondary is the unRAID array if [[ -n $shareCachePool && -d "/mnt/$shareCachePool" ]]; then for SHAREPATH in /mnt/disk[1-9]/"$SHARE"/ /mnt/disk[1-9][0-9]/"$SHARE"/ ; do move "$SHAREPATH" done fi fi fi done # maybe empty an array disk EMPTYING=$(grep '^shareUserEmptying=' /boot/config/share.cfg | cut -d'"' -f2) IFS=',' read -ra arr <<< "$EMPTYING" for DISK in "${arr[@]}"; do # we can only empty share directories for SHAREPATH in /mnt/$DISK/* ; do SHARE=$(basename "$SHAREPATH") if [[ -d "$SHAREPATH" && -f "$CFGPATH/$SHARE.cfg" ]]; then move "$SHAREPATH" fi done # output list of files which could not be moved # use 'find' in case huge number of files left in /mnt/$DISK count=$(find /mnt/$DISK -mindepth 1 | wc -l) if [ "$count" -gt 0 ]; then find /mnt/$DISK -mindepth 1 -depth -printf 'move: %p Not moved\n' | head -n 100 if [ "$count" -gt 100 ]; then echo "[output truncated to first 100 entries]" fi fi done rm -f $PIDFILE echo "mover: finished" >&2 } killtree() { local pid=$1 child for child in $(pgrep --ns $$ -P $pid); do killtree $child done [ $pid -ne $$ ] && kill -TERM $pid } stop() { if [ ! -f $PIDFILE ]; then echo "mover: not running" exit 0 fi killtree $(cat $PIDFILE) sleep 2 rm -f $PIDFILE echo "mover: stopped" } # display usage and then exit usage() { echo "Usage: $0 start|stop|status" exit 1 } if [ "$#" -ne 1 ]; then usage exit 1 fi case $1 in start) start ;; stop) stop ;; status) [ -f $PIDFILE ] ;; *) usage ;; esac