You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
7.2 KiB

  1. #!/bin/bash
  2. #:-
  3. . /etc/shlib
  4. #:-
  5. include common
  6. include parse
  7. include process
  8. depends shyaml lock
  9. [ "$UID" != "0" ] && echo "You must be root." && exit 1
  10. ##
  11. ## Here's an example crontab:
  12. ##
  13. ## SHELL=/bin/sh
  14. ## PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
  15. ##
  16. ## 49 */2 * * * root mirror-dir -d core-05.0k.io:10023 -u rsync /etc /home /opt/apps 2>&1 | logger -t mirror-dir
  17. ##
  18. usage="usage: $exname -d DEST1 [-d DEST2 [...]] [-u USER] [DIR1 [DIR2 ...]]
  19. Preserve as much as possible the source structure, keeping hard-links, acl,
  20. exact numerical uids and gids, and being able to resume in very large files.
  21. Options:
  22. DIR1 ... DIRn
  23. Local directories that should be mirrored on destination(s).
  24. examples: /etc /home /var/backups
  25. If no directories are provided, the config file root
  26. entries will be used all as destination to copy.
  27. -d DESTn
  28. Can be repeated. Specifies host destination towards which
  29. files will be send. Note that you can specify port number after
  30. a colon and a bandwidth limit for rsync after a '/'.
  31. examples: -d liszt.musicalta:10022 -d 10.8.0.19/200
  32. -u USER (default: 'rsync')
  33. Local AND destination user to log as at both ends to transfer file.
  34. This local user need to have a NOPASSWD ssh login towards it's
  35. account on destination. This destination account should have
  36. full permissions access without passwd to write with rsync-server
  37. in the destination directory.
  38. -h STORE (default is taken of the hostname file)
  39. Set the destination store, this is the name of the directory where
  40. the files will all directories will be copied. Beware ! if 2 hosts
  41. use the same store, this means they'll conflictingly update the
  42. same destination directory. Only use this if you know what you
  43. are doing.
  44. "
  45. dests=()
  46. source_dirs=()
  47. hostname=
  48. while [ "$#" != 0 ]; do
  49. case "$1" in
  50. "-d")
  51. dests+=("$2")
  52. shift
  53. ;;
  54. "-h")
  55. hostname="$2"
  56. shift
  57. ;;
  58. "-u")
  59. user="$2"
  60. shift
  61. ;;
  62. *)
  63. source_dirs+=("$1")
  64. ;;
  65. esac
  66. shift
  67. done
  68. if test -z "$hostname"; then
  69. hostname=$(hostname)
  70. fi
  71. if test -z "$hostname"; then
  72. die "Couldn't figure a valid hostname. Please specify one with \`\`-h STORENAME\`\`."
  73. fi
  74. user=${user:-rsync}
  75. dest_path=/var/mirror/$hostname
  76. config_file="/etc/$exname/config.yml"
  77. if [ "${#source_dirs[@]}" == 0 ]; then
  78. if [ -e "$config_file" ]; then
  79. echo "No source provided on command line.. "
  80. echo " ..so reading '$config_file' for default sources..."
  81. source_dirs=($(eval echo $(shyaml get-values default.sources < "$config_file")))
  82. fi
  83. if [ "${#source_dirs[@]}" == 0 ]; then
  84. err "You must specify at least one source directory to mirror" \
  85. "on command line (or in a config file)."
  86. print_usage
  87. exit 1
  88. fi
  89. fi
  90. echo "Sources directories are: ${source_dirs[@]}"
  91. if [ "${#dests[@]}" == 0 ]; then
  92. err "You must specify at least a destination."
  93. print_usage
  94. exit 1
  95. fi
  96. rsync_options=(${RSYNC_OPTIONS:-})
  97. ssh_options=(${SSH_OPTIONS:-})
  98. get_exclude_patterns() {
  99. local dir="$1"
  100. [ -e "$config_file" ] || return
  101. cat "$config_file" | shyaml get-values-0 "${dir//.\\./}.exclude" 2>/dev/null
  102. }
  103. for dest in "${dests[@]}"; do
  104. for d in "${source_dirs[@]}"; do
  105. current_rsync_options=("${rsync_options[@]}")
  106. if [[ "$dest" == *"/"* ]]; then
  107. current_rsync_options+=("--bwlimit" "${dest##*/}")
  108. dest="${dest%/*}"
  109. fi
  110. if [[ "$dest" == *":"* ]]; then
  111. ssh_options+=("-p" "${dest#*:}")
  112. dest="${dest%%:*}"
  113. fi
  114. dirpath="$(dirname "$d")"
  115. if [ "$dirpath" == "/" ]; then
  116. dir="/$(basename "$d")"
  117. else
  118. dir="$dirpath/$(basename "$d")"
  119. fi
  120. [ -d "$dir" ] || {
  121. warn "ignoring '$dir' as it is not existing."
  122. continue
  123. }
  124. lock_label=$exname-$hostname-$(echo "$dest$d" | md5_compat | cut -c 1-6)
  125. tmp_exclude_patterns=/tmp/${lock_label}.exclude_patterns.tmp
  126. ## Adding the base of the dir if required... seems necessary with
  127. ## the rsync option that replicate the full path.
  128. has_exclude_pattern=
  129. while read-0 exclude_dir; do
  130. if [ -z "$has_exclude_pattern" ]; then
  131. echo "Adding exclude patterns..." >&2
  132. has_exclude_pattern=1
  133. fi
  134. if [[ "$exclude_dir" == "/"* ]]; then
  135. exclude_dir="$dir${exclude_dir}"
  136. fi
  137. echo " - $exclude_dir" >&2
  138. p0 "$exclude_dir"
  139. done < <(get_exclude_patterns "$dir") > "$tmp_exclude_patterns"
  140. if [ -n "$has_exclude_pattern" ]; then
  141. current_rsync_options+=("-0" "--exclude-from"="$tmp_exclude_patterns")
  142. else
  143. echo "No exclude patterns for '$dir'."
  144. fi
  145. echo ---------------------------------
  146. echo "Starting rsync: $d -> $dest ($(date))"
  147. echo nice -n 15 \
  148. rsync "${current_rsync_options[@]}" -azvARH \
  149. -e "'sudo -u $user ssh ${ssh_options[*]}'" \
  150. --delete --delete-excluded \
  151. --partial --partial-dir .rsync-partial \
  152. --numeric-ids "$dir/" "$user@$dest":"$dest_path"
  153. start="$SECONDS"
  154. retry=1
  155. while true; do
  156. lock "$lock_label" -v -D -k -- \
  157. nice -n 15 \
  158. rsync "${current_rsync_options[@]}" -azvARH \
  159. -e "sudo -u $user ssh ${ssh_options[*]}" \
  160. --delete --delete-excluded \
  161. --partial --partial-dir .rsync-partial \
  162. --numeric-ids "$dir/" "$user@$dest":"$dest_path"
  163. errlvl="$?"
  164. case "$errlvl" in
  165. 20) ## Received SIGUSR1, SIGINTT
  166. echo "!! Rsync received SIGUSR1 or SIGINT."
  167. echo " .. Full interruption while $d -> $dest and after $((SECONDS - start))s"
  168. break 2
  169. ;;
  170. 137|143) ## killed SIGKILL, SIGTERM, SIGINT
  171. echo "!! Rsync received $(kill -l "$errlvl")"
  172. echo " .. Full interruption while $d -> $dest and after $((SECONDS - start))s"
  173. break 2
  174. ;;
  175. 0)
  176. echo "Rsync finished with success $d -> $dest in $((SECONDS - start))s"
  177. break
  178. ;;
  179. *)
  180. echo "!! Rsync failed with an errorlevel $errlvl after $((SECONDS - start))s since start."
  181. if [ "$retry" -lt 3 ]; then
  182. echo "!! Triggering a retry ($((++retry))/3)"
  183. continue
  184. else
  185. echo "!! Tried 3 times, bailing out."
  186. echo " .. interruption of $d -> $dest after $((SECONDS - start))s"
  187. break
  188. fi
  189. ;;
  190. esac
  191. done
  192. if [ -n "$has_exclude_pattern" ]; then
  193. rm -fv "$tmp_exclude_patterns"
  194. fi
  195. done
  196. done