Sync với cloud drive (III)

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.

Comments Off on Sync với cloud drive (III)

Filed under Software

Comments are closed.