Script sau đây giám sát thư mục LOCAL_DIR và cả target của các symlink trong đó. Nhờ vậy, mọi thay đổi trong cả LOCAL_DIR và symlink đều được cập nhật lên REMOTE_DIR, thậm chí nhiều REMOTE_DIR.
Một LOCAL_DIR với nhiều symlink bên trong rõ ràng là tiện lợi hơn nhiều LOCAL_DIR. Code phức tạp hơn một chút nhưng hiệu quả chỉ bị ảnh hưởng bởi tốc độ upload!
#!/bin/bash
# Script cloudup, version 20191126
# © 2019 LNT <lnt@lyle.info>
#
# Cập nhật file/thư mục có thay đổi vào ổ đĩa đám mây
lockfile="/run/$(basename $0).pid"
trap 'xc=$?; sudo rm -f "$lockfile"; rm -rf $SHM; [ $xc -eq 3 ] && $0 $@' EXIT
echo -e "DATE:$(date)\nPID:$$" | sudo tee "$lockfile" > /dev/null | exit 2
function usage() {
cat >&2 <<EOF
Cập nhật lên ổ đĩa đám mây ->> Chấp nhận symlink <<-
usage: $(basename $0) LOCAL_DIR REMOTE_DIR^REMOTE_DIR2...
LOCAL_DIR: thư mục được giám sát, có thể chứa symlink nhưng không là symlink
REMOTE_DIR: một hay nhiều ổ đĩa đám mây
EOF
echo -e "[✘] Lỗi: $1\n"
exit 2
}
SNAME=$(basename $0)
LOG_FILE=/tmp/$SNAME.log
OPT='-L -u --fast-list --transfers=20 --checkers=20 --tpslimit=20 --drive-chunk-size=1M'
LOG="--log-file=$LOG_FILE"
SHM=$(mktemp -d /dev/shm/$SNAME-XXXXXXXX)
[ $# -ne 2 ] && usage "LOCAL_DIR hay REMOTE_DIR không hợp lệ!"
LOCAL_DIR="$1"
REMOTE_DIR="$2"
declare -A lstDir
dup=()
oIFS=$IFS;IFS='^';LOCALS=($LOCAL_DIR);REMOTES=($REMOTE_DIR);printf "%s\n" ${REMOTES[@]}>$SHM/remotes;IFS=$oIFS
# -->> Bỏ # ở 3 dòng sau để kiểm tra REMOTE_DIR <<--#
# for remote in ${REMOTES[@]}; do
# rclone lsd $remote &> /dev/null || usage "REMOTE_DIR '$remote' không xác định!"
# done
[ -e $LOCAL_DIR ] && lstDir[$LOCAL_DIR]=$LOCAL_DIR || usage "$local không tồn tại!"
while read -r link; do
target=$(readlink -f "$link")
if [ -z "${lstDir[$target]+'OK'}" ]; then
lstDir[$target]=$link
else
dup+=("$link")
targets=${!lstDir[*]}
[[ " ${targets// /\ } " =~ " ${target// /\\ } " ]] && dup+=("${lstDir[$target]}")
fi
done < <(find -L "$LOCAL_DIR" -xtype l -exec test -e {} \; -print)
if (( ${#dup[@]} )); then
tmp="Vài symlink trỏ đến cùng target, vui lòng xóa symlink thừa..."
for link in ${dup[@]}; do
tmp="$tmp\n\t- $link => $(readlink -f "$link")"
done
usage "$tmp"
fi
sudo sysctl fs.inotify.max_user_watches=524288 &> /dev/null
sudo sysctl -p &> /dev/null
lst=$(mktemp)
printf "%s\n" ${!lstDir[@]} > $lst
restart=0
inotifywait -mr --event 'create,close_write,delete,move,move_self' --format '%e %w^%f' --fromfile "$lst" |
while read evt full; do
oIFS=$IFS; IFS='^'; read wf ef <<< $full; IFS=$oIFS
if [[ -L "$wf$ef" && -z "${lstDir[$(readlink -f "$wf$ef")]+OK}" ]]; then
file=$wf$ef
else
for pat in ${!lstDir[@]}; do
[[ "$wf$ef" =~ ^"$pat" ]] && tmp="$wf$ef" && wf="$pat" && ef=${tmp#"$wf"} && break
done
file=${lstDir[$wf]}$ef
fi
while read remote; do
rfile=${file/$LOCAL_DIR/$remote}
case $evt in
CREATE)
[[ ! -L "$file" ]] && continue
target=$(readlink -f "$file")
if [ -z "${lstDir[$target]+OK}" ]; then
[[ -d $file && -z "$(ls -A $file)" ]] && rclone mkdir "$rfile" || rclone copyto "$file" "$rfile" $OPT $LOG
restart=1
else
old=${lstDir[$target]}
rm -f "$old" &> /dev/null
rclone moveto "${old/$LOCAL_DIR/$remote}" "${file/$LOCAL_DIR/$remote}" $OPT $LOG &> /dev/null
lstDir[$target]="$file" && echo 1 > $SHM/$remote
fi
;;
CLOSE_WRITE,CLOSE)
rclone copyto "$file" "$rfile" $OPT $LOG
;;
CREATE,ISDIR)
[ "$(ls -A "$file")" ] && rclone copyto "$file" "$rfile" $OPT $LOG || rclone mkdir "$rfile"
;;
DELETE)
moved=$(<$SHM/$remote) && >$SHM/$remote
if [ ! $moved ]; then
rclone delete "$rfile" $OPT $LOG
[[ " ${lstDir[*]// /\ } " =~ " ${file// /\\ } " ]] && restart=1
fi
;;
MOVED_FROM,ISDIR)
echo "$file" > $SHM/$remote
;;
MOVED_FROM)
echo "$file" > $SHM/$remote
(
sleep .2
afile=$(<$SHM/$remote) && >$SHM/$remote
if [ ! -z "$afile" ]; then
[ -d "$afile" ] && cmd=purge || cmd=delete
rclone $cmd "${afile/$LOCAL_DIR/$remote}" $OPT $LOG &> /dev/null
[[ " ${lstDir[*]// /\ } " =~ " ${file// /\\ } " ]] && restart=1
fi
) &
;;
MOVED_TO)
ofile=$(<$SHM/$remote) && >$SHM/$remote
if [ -z "$ofile" ]; then
rclone copyto "$file" "$rfile" $OPT $LOG
[[ -L "$file" ]] && restart=1
else
rclone moveto "${ofile/$LOCAL_DIR/$remote}" "$rfile" $OPT $LOG
[[ -L "$file" ]] && lstDir[$(readlink -f "$file")]=$file
fi
;;
MOVED_TO,ISDIR)
ofile=$(<$SHM/$remote) && >$SHM/$remote
[ -z "$ofile" ] && rclone copyto "$file" "$rfile" $OPT $LOG || rclone moveto "${ofile/$LOCAL_DIR/$remote}" "$rfile" $OPT $LOG
;;
MOVE_SELF)
ofile=$(<$SHM/$remote) && >$SHM/$remote
[ -z "$ofile" ] || rclone purge "$rfile" $LOG
;;
esac
done < ${SHM}/remotes
(( $restart )) && kill -9 $(pgrep inotifywait)
done
exit 3
Kịch bản như sau:
Trên máy Office, chúng ta có các thư mục Documents, Projects, Drafts nằm lẫn trong nhiều file và thư mục khác nhau, nhưng chúng ta chỉ muốn backup 3 thư mục này thôi.
Vậy chúng ta tạo thư mục Cloud và tạo symlink của 3 thư mục trên vào Cloud. Sau đó cho cloudup giám sát Cloud để cập nhật vào Onedrive.
@reboot /path/to/cloudup /path/to/Cloud Onedrive:Office
Từ đó, mọi thay đổi trong 3 thư mục trên sẽ được cập nhật vào thư mục Office của Onedrive, bất kể chúng ta làm việc trong thư mục Cloud hay trong 3 thư mục nói trên.
- Khi xóa symlink, inotifywait không tự động bỏ giám sát target tương ứng, khi đó script tự khởi động lại để giải phóng tài nguyên hệ thống,
- Khi thêm symlink, script cũng tự khởi động lại để inotifywait giám sát thêm target tương ứng.
- Nếu có nhiều symlink của cùng target, script sẽ yêu cầu xóa bớt hoặc script sẽ tự xóa symlink cũ hơn.
- Di chuyển symlink bên trong thư mục được giám sát không làm script khởi động lại.