#!/usr/bin/bash

set -o pipefail
shopt -s inherit_errexit 2>/dev/null || true  # bash 4.x compatibility

# should always run with superuser privildges
[ "$UID" -ne 0 ] && exec sudo "$0" "$@"

if [ $# -eq 0 ]; then
  echo "Please, use the: '$(basename $0) -h' to see usage message"
  exit 1
fi

export COLUMNS=250
export LINES=50

REQUIRED_TOOLS="
  angie
  awk
  grep
  getopt
  rsync
  sudo
"

CONFILE="/etc/angiehasync/angiehasync.conf"

dryrun=
mdif=

AVER=""
CH_FILES_LIST=""
DEL_FILES_LIST=""
LOCAL_RUN=""
R_STAT=""
SSH_USER="root"
. $CONFILE

log() {
    echo "$(date +'%Y-%m-%d %H:%M:%S') $@" >&2
}
info() {
    echo "$@" >&1
    echo "$(date +'%Y-%m-%d %H:%M:%S') INFO: $@" >&2
}
warn() {
    echo "$@" >&1
    echo "$(date +'%Y-%m-%d %H:%M:%S') WARN: $@" >&2
}
err() {
    echo "$@" >&1
    echo "$(date +'%Y-%m-%d %H:%M:%S') FATAL: $@" >&2
    exit 1
}

syntx_t() {
  sudo angie -t
}

if_run() {
  r=$(ps aux | grep -v awk | awk -F '#' '/angie: master/{print $2}' | grep -E -o '[0-9]*')
  if [ -z $r ]; then
    err "Angie has not been started on $M_NODE!"
  fi
}

h_check() {
  node=$1
  rdeb=
  [ $debug = yes ]   && rdeb="--debug"
  rrsync=$(ssh $sshopts "$SSH_USER@$node" "sudo which rsync" 2>&1)
  raver=$(ssh $sshopts "$SSH_USER@$node" "sudo angie -v" 2>&1)
  ssh $sshopts "$SSH_USER@$node" "sudo angiehasync --ifrun $rdeb"
  if [ $? -eq 0 ]; then
      if [[ -n "$rrsync" && "$AVER" = "$raver" ]]; then
          return 0
      fi
  else
      TARGET_HOSTS=$(echo "$TARGET_HOSTS" | sed s/$node//g)
      warn "Node $node has been excluded from sync due to errors. Please, run the script with --debug for more details."
  fi
}

r_check() {
  if if_run; then
    mpgo=$(ps aux | awk -F '#' '/angie: worker/{print $2}' | grep -E -o '[0-9]*' | head -1)
    kill -s HUP $(cat /run/angie.pid)
    local loop=0
    while [ $loop -lt $ANGIE_RELOAD_TIMEOUT ]; do
      echo "Checking if Angie reloaded on $M_NODE (#$loop)"
      mpgn=$(ps aux | awk -F '#' '/angie: worker/{print $2}' | grep -E -o '[0-9]*' | head -1)
      if [ $mpgn -ne $mpgo ]; then
        break
      fi
      sleep 1
      loop=$((loop+1))
    done
    if [ $loop -ge $ANGIE_RELOAD_TIMEOUT ]; then
      if [ -n "$R_STAT" ]; then
        warn "Local Angie reload failed! Might be need manual RESTART."
        R_STAT='RESTART'
      else
        err "Local Angie reload failed on $M_NODE! The generation of the master process has not been changed. New configuration has not been applied."
      fi
    fi
  else
    err "Angie has not been started on $M_NODE!"
  fi
}

ch_diff() {
  dif=$1
  echo ""
  echo -e "Comparing $node:$dif (<) with $M_NODE:$dif (>)"
  echo ""
  ssh $sshopts "$SSH_USER@$node" "cat $dif" | diff --label "$node:$dif" - "$dif" | cat
}

ch_files() {
  node=$1
  CH_FILES_LIST=""
  DEL_FILES_LIST=""
  drsync=$(rsync -aid --delete --dry-run --timeout=$SYNC_TIMEOUT --exclude={*.swp,*.swx} $WATCH_DIR "$SSH_USER@$node:$WATCH_DIR")
  if [ $? -ne 0 ]; then
    warn "Cannot connect to node $node"
    return 1
  else
    DEL_FILES_LIST=$(echo "$drsync" | grep -E '^\*deleting' | awk '{print $2}' | xargs -I {} echo "$WATCH_DIR{} ")
    CH_FILES_LIST=$(echo "$drsync" | grep -E '^<' | awk '{print $2}' | xargs -I {} echo "$WATCH_DIR{} ")
  fi
  if [ -z "$CH_FILES_LIST" ]; then
    info "Nothing to sync on node $node"
    return 1
  else
    if [[ "$mdif" = 'yes' ]]; then
      [ -n "$DEL_FILES_LIST" ] && echo "Following files will be removed on remote node $node: 
===============
$DEL_FILES_LIST
==============="
      for dif in $CH_FILES_LIST; do
        ch_diff "$dif"
      done
    else
      info "Local configuration has differences with node $node, you can use the -d option to see the diff"
    fi
  fi
  return 0
}

afch_files() {
  node=$1
  CH_FILES_LIST=""
  DEL_FILES_LIST=""
  drsync=$(rsync -aid --delete --dry-run --timeout=$SYNC_TIMEOUT --exclude={*.swp,*.swx} $WATCH_DIR "$SSH_USER@$node:$WATCH_DIR")
    DEL_FILES_LIST=$(echo "$drsync" | grep -E '^\*deleting' | awk '{print $2}' | xargs -I {} echo "$WATCH_DIR{} ")
    CH_FILES_LIST=$(echo "$drsync" | grep -E '^<' | awk '{print $2}' | xargs -I {} echo "$WATCH_DIR{} ")
  if [[ -n "$CH_FILES_LIST" || -n "$DEL_FILES_LIST" ]]; then
    return 1
  fi
  return 0
}

sync() {
  syntx_t
  if [ $? -eq 0 ]; then
    echo "Angie configuration syntax tested successfully"
    R_STAT='ok'
    r_check
  else
    err "Angie configuration test FAILED!"
  fi

  for node in $TARGET_HOSTS; do
    if ch_files $node; then
      ifreload=$( 2>&1)
      if [ -z "$dryrun" ]; then
        ssh $sshopts "$SSH_USER@$node" \
        "sudo rsync -aid --delete --timeout=$SYNC_TIMEOUT --exclude={*.swp,*.swx} $M_NODE::angie $WATCH_DIR >&2"
        [ $? -ne 0 ] && warn "Cannot sync with node $node"
        if [ $R_STAT == 'RESTART' ]; then
          warn "New configuration on remote node $node has not been applied on remote host. Might be need manual RESTART."
        elif ! afch_files $node; then
          warn "New configuration on remote node $node has not been sync for some reason. Please, run the script with --debug for more details."
        else
          rdeb=
          [ $debug = yes ]   && rdeb="--debug"
          ssh $sshopts "$SSH_USER@$node" "sudo angiehasync --ifreload $rdeb" 2>&1 && \
          echo "Angie reloaded on $node."
        fi
      fi
    fi
  done
  info "Done!"
}

main() {

  sargs="$0 $@"

  local args
  args=$(
    opts=$(echo "
      config:
      dryrun
      diff
      help
      node:
      sync
      debug
      ifrun
      ifreload
    " | sed -e 's/^[[:blank:]]*//' -e '/^$/d' | paste -d, -s -)
    env PATH=/usr/local/bin:$PATH getopt -l "$opts" -o "+c:Ddhn:S" -- "$@"
  ) || usage

  eval set -- "$args"

  local chreload=no chrun=no debug=no help=no sync=no tnodes=""
  while :; do
      case "$1" in
          -c|--config)
              CONFILE=$2
              . $CONFILE
              shift 2;;
          -D|--dryrun) 
              dryrun=yes
              shift;;
          -d|--diff) 
              mdif=yes
              shift;;
          -h|--help)
              help=yes
              shift;;
          -n|--node)
              tnodes=$2
              shift 2;;
          -S|--sync)
              sync=yes
              shift;;
          --debug) 
              debug=yes
              shift;;
          --ifreload)
              LOCAL_RUN=yes
              chreload=yes
              shift;;
          --ifrun)
              LOCAL_RUN=yes
              chrun=yes
              shift;;
          --)
              shift; break;;
      esac
  done

  sshopts="-o StrictHostKeyChecking=no -o ConnectTimeout="$SSH_TIMEOUT" -p $SSH_PORT "

  [ ! -f "$LOG_FILE" ] && mkdir -p "${LOG_FILE%/*}" && touch "$LOG_FILE"

  exec 3>&1 2>>"$LOG_FILE"

  log "Started as $sargs"

  local ok=yes tool
  for tool in $REQUIRED_TOOLS; do
    which "$tool" >/dev/null && continue
    warn "$tool is not found"
    ok=no
  done
  [ $ok = yes ] || err "Please install all the required tools"

  AVER=$(angie -v 2>&1)

  if [ -n "$tnodes" ]; then
    TARGET_HOSTS=$tnodes
  fi

  [ $help = yes ]          && usage
  [ $debug = yes ]         && set -x
  if [ -z "$M_NODE" ]; then
    err "Please, define the hostname/IP this node in configuration file."
  fi

  if [ -z $LOCAL_RUN ]; then
    if [ -z "$TARGET_HOSTS" ]; then
      err "Please, configure at least one neighbour node to sync."
    else
      eval `ssh-agent -s`
      ssh-add "$SSH_ID"
      for node in $TARGET_HOSTS; do
        h_check $node
      done
    fi
  [ $sync = yes ]    && sync || true
  fi

  [ $chreload = yes ]      && r_check
  [ $chrun = yes ]         && if_run
}

usage() {
  cat <<EOM

Angie configuration synchronization script

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃          Before run the sync, please make sure:           ┃
┃1. rsync is installed on the master node and target node(s)┃
┃2. Angie versions are the same                             ┃
┃3. Angie is running on the master node and target node(s)  ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

USAGE:

% $(basename "$0") [options]

  options:
    -c PATH_TO, --config=PATH_TO        use the custom config file, default: '/etc/angiehasync/angiehasync.conf'
    -D,         --dryrun                run in dry-run mode (use this option with -S, --sync)
    -d,         --diff                  enable diff output with remote hosts
    -h,         --help                  show this help message
    -n NODE_TO, --node=NODE_TO          use to sync specified node(s)
    -S,         --sync                  perform synchronization
    --debug                             enable debug
EOM
  exit 0
}

main "$@"
