|
| 1 | +#!/usr/bin/env bash |
| 2 | +_sdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" |
| 3 | +set -eu |
| 4 | + |
| 5 | +show_help(){ |
| 6 | + cat <<HELP |
| 7 | + $(basename $0) -c path/to/config |
| 8 | +
|
| 9 | + Synchronizes the directory that the configuration file present with \$dest_dir |
| 10 | + Always excluded paths: '.git' |
| 11 | +
|
| 12 | + Options: |
| 13 | +
|
| 14 | + --dry-run : Runs RSYNC in dry run mode. |
| 15 | +
|
| 16 | + Configuration (a valid BASH script): |
| 17 | +
|
| 18 | + proxy_host="USER@HOST:PORT" # SSH host to use as the jump server |
| 19 | + proxy_host="foo" # Same as above, use "foo" target from .ssh/config |
| 20 | +
|
| 21 | + dest_host="USER@HOST:PORT" # Destination host |
| 22 | + dest_host="bar" # Destination host from .ssh/config |
| 23 | +
|
| 24 | + dest_dir='./path/to/dest_dir/' # Notice the / at the end |
| 25 | +
|
| 26 | + use_gitignore=true # use .gitignore file to extract exclude dirs |
| 27 | + run_before_sync+=("path/to/hookscript") # execute those scripts before sync |
| 28 | +
|
| 29 | +HELP |
| 30 | +} |
| 31 | + |
| 32 | +die(){ |
| 33 | + >&2 echo |
| 34 | + >&2 echo "$@" |
| 35 | + exit 1 |
| 36 | +} |
| 37 | + |
| 38 | +help_die(){ |
| 39 | + >&2 echo |
| 40 | + >&2 echo "$@" |
| 41 | + show_help |
| 42 | + exit 1 |
| 43 | +} |
| 44 | + |
| 45 | +# Parse command line arguments |
| 46 | +# --------------------------- |
| 47 | +# Initialize parameters |
| 48 | +config= |
| 49 | +dry_run= |
| 50 | +# --------------------------- |
| 51 | +args_backup=("$@") |
| 52 | +args=() |
| 53 | +_count=1 |
| 54 | +while [ $# -gt 0 ]; do |
| 55 | + key="${1:-}" |
| 56 | + case $key in |
| 57 | + -h|-\?|--help|'') |
| 58 | + show_help # Display a usage synopsis. |
| 59 | + exit |
| 60 | + ;; |
| 61 | + # -------------------------------------------------------- |
| 62 | + -c|--config) shift |
| 63 | + config="$1" |
| 64 | + ;; |
| 65 | + --dry-run) |
| 66 | + dry_run="--dry-run" |
| 67 | + ;; |
| 68 | + # -------------------------------------------------------- |
| 69 | + -*) # Handle unrecognized options |
| 70 | + help_die "Unknown option: $1" |
| 71 | + ;; |
| 72 | + *) # Generate the new positional arguments: $arg1, $arg2, ... and ${args[@]} |
| 73 | + if [[ ! -z ${1:-} ]]; then |
| 74 | + declare arg$((_count++))="$1" |
| 75 | + args+=("$1") |
| 76 | + fi |
| 77 | + ;; |
| 78 | + esac |
| 79 | + [[ -z ${1:-} ]] && break || shift |
| 80 | +done; set -- "${args_backup[@]-}" |
| 81 | +# Use $arg1 in place of $1, $arg2 in place of $2 and so on, |
| 82 | +# "$@" is in the original state, |
| 83 | +# use ${args[@]} for new positional arguments |
| 84 | + |
| 85 | +[[ -f "$config" ]] || die "Configuration file is required." |
| 86 | +source "$config" |
| 87 | + |
| 88 | +SRC_DIR="$(dirname "$config")" |
| 89 | + |
| 90 | +read SGW_USERNAME SGW_HOST SGW_PORT_ON_SERVER <<< $(echo $dest_host | sed 's/@/ /' | sed 's/:/ /') |
| 91 | +[[ -z $SGW_HOST ]] && { SGW_HOST=$SGW_USERNAME; SGW_USERNAME=''; } |
| 92 | +read PROXY_USERNAME PROXY_HOST PROXY_PORT <<< $(echo $proxy_host | sed 's/@/ /' | sed 's/:/ /') |
| 93 | +[[ -z $PROXY_HOST ]] && { PROXY_HOST=$PROXY_USERNAME; PROXY_USERNAME=''; } |
| 94 | + |
| 95 | +ignores=(--exclude '.git') |
| 96 | +gitignore_file="$SRC_DIR/.gitignore" |
| 97 | +if ${use_gitignore:-false} && [[ -f "$gitignore_file" ]]; then |
| 98 | + while IFS=: read -r line; do |
| 99 | + ignores+=(--exclude "$line") |
| 100 | + done < <(grep "" "$gitignore_file") |
| 101 | +fi |
| 102 | + |
| 103 | +script_name="$(basename $0)" |
| 104 | + |
| 105 | +echo_blue () { |
| 106 | + echo -e "\e[1;34m$*\e[0m" |
| 107 | +} |
| 108 | + |
| 109 | +echo_yellow () { |
| 110 | + echo -e "\e[1;33m$*\e[0m" |
| 111 | +} |
| 112 | + |
| 113 | +echo_green () { |
| 114 | + echo -e "\e[1;32m$*\e[0m" |
| 115 | +} |
| 116 | + |
| 117 | +RSYNC="nice -n19 ionice -c3 rsync" |
| 118 | + |
| 119 | +timestamp(){ |
| 120 | + date +'%Y-%m-%d %H:%M' |
| 121 | +} |
| 122 | + |
| 123 | +previous_sync_failed=false |
| 124 | +while :; do |
| 125 | + hook_failed=false |
| 126 | + for cmd in "${run_before_sync[@]}"; do |
| 127 | + echo_blue "Running hook before sync: $cmd" |
| 128 | + eval $cmd || { hook_failed=true; break; } |
| 129 | + done |
| 130 | + |
| 131 | + if ! $hook_failed; then |
| 132 | + echo_blue "$(timestamp): Synchronizing..." |
| 133 | + |
| 134 | + [[ -z $dry_run ]] || set -x |
| 135 | + if $RSYNC -avzhP --delete $dry_run "${ignores[@]}" \ |
| 136 | + -e "ssh -A -J ${PROXY_HOST} -p ${SGW_PORT_ON_SERVER}" \ |
| 137 | + "$SRC_DIR" \ |
| 138 | + ${SGW_USERNAME}@localhost:"${dest_dir}"; then |
| 139 | + |
| 140 | + $previous_sync_failed && notify-send -u critical "$script_name Succeeded." "$(timestamp)" |
| 141 | + else |
| 142 | + period=10 |
| 143 | + $previous_sync_failed || notify-send -u critical "$script_name Failed." "Retrying in $period seconds." |
| 144 | + sleep $period |
| 145 | + echo_yellow "Retrying..." |
| 146 | + previous_sync_failed=true |
| 147 | + continue |
| 148 | + fi |
| 149 | + $previous_sync_failed || notify-send "Sync done." "$(timestamp): ${dest_dir}" |
| 150 | + |
| 151 | + previous_sync_failed=false |
| 152 | + else |
| 153 | + notify-send -u critical "$script_name Failed." "$cmd failed ($(timestamp))" |
| 154 | + fi |
| 155 | + |
| 156 | + echo_green "Waiting for directory changes..." |
| 157 | + inotifywait -q -e modify,create,delete -r "$SRC_DIR" |
| 158 | +done |
0 commit comments