endef
+define Package/keepalived-sync
+ SECTION:=net
+ CATEGORY:=Network
+ TITLE:=Keepalived Master and Backup Synchronization
+ DEPENDS:= +keepalived +rsync +inotifywait +sudo +@BUSYBOX_CUSTOM +@BUSYBOX_CONFIG_TIMEOUT
+endef
+
+define Package/keepalived-sync/description
+ Keepalived HA with Master to Backup files and data Synchronization
+endef
+
+define Package/keepalived-sync/conffiles
+/etc/keepalived/scripts
+/etc/keepalived/keys
+endef
+
+define Package/keepalived-sync/install
+ $(INSTALL_DIR) $(1)/etc/init.d
+ $(INSTALL_BIN) ./files/etc/init.d/keepalived-inotify \
+ $(1)/etc/init.d/keepalived-inotify
+
+ $(INSTALL_DIR) $(1)/usr/share/keepalived/scripts
+ $(INSTALL_BIN) ./files/usr/share/keepalived/scripts/rsync.sh \
+ $(1)/usr/share/keepalived/scripts/rsync.sh
+
+ $(INSTALL_DIR) $(1)/etc/keepalived/scripts
+ $(LN) /usr/share/keepalived/scripts/rsync.sh \
+ $(1)/etc/keepalived/scripts/rsync.sh
+
+ $(INSTALL_DIR) $(1)/usr/bin
+ $(INSTALL_BIN) ./files/usr/bin/keepalived-rsync-inotify \
+ $(1)/usr/bin/keepalived-rsync-inotify
+
+ $(INSTALL_DIR) $(1)/lib/functions/keepalived
+ $(INSTALL_DATA) ./files/lib/functions/keepalived/hotplug.sh \
+ $(1)/lib/functions/keepalived/hotplug.sh
+ $(INSTALL_DATA) ./files/lib/functions/keepalived/common.sh \
+ $(1)/lib/functions/keepalived/common.sh
+
+ $(INSTALL_DIR) $(1)/usr/libexec/keepalived/rpc
+ $(INSTALL_DATA) ./files/usr/libexec/keepalived/rpc/sync.sh \
+ $(1)/usr/libexec/keepalived/rpc/sync.sh
+
+ $(INSTALL_DIR) $(1)/etc/hotplug.d/keepalived
+ $(CP) ./files/etc/hotplug.d/keepalived/* \
+ $(1)/etc/hotplug.d/keepalived
+endef
+
+USER=keepalived
+USER_ID=60001
+USER_HOME=/usr/share/keepalived/rsync
+SUDO_DIR=/etc/sudoers.d
+SUDO_FILE=$(SUDO_DIR)/$(USER)
+KEYS_DIR=/etc/keepalived/keys
+
+define Package/keepalived-sync/postinst
+ #!/bin/sh
+
+ mkdir -p "$${IPKG_INSTROOT}/etc/uci-defaults"
+ DEFAULT_SCRIPT="$${IPKG_INSTROOT}/etc/uci-defaults/99-keepalived-sync"
+
+ cat << EOF > $${DEFAULT_SCRIPT}
+#!/bin/sh
+
+. /lib/functions.sh
+
+mkdir -p $(KEYS_DIR)
+
+group_add "$(USER)" "$(USER_ID)"
+user_add "$(USER)" "$(USER_ID)" "$(USER_ID)" "$(USER)" "$(USER_HOME)" "/bin/ash"
+
+mkdir -m 700 -p "$(USER_HOME)"
+mkdir -m 700 -p "$(USER_HOME)/.ssh"
+chown "$(USER)":"$(USER)" "$(USER_HOME)" -R
+
+[ ! -d "$(SUDO_DIR)" ] && mkdir "$(SUDO_DIR)"
+echo "$(USER) ALL= NOPASSWD:/usr/bin/rsync" > "$(SUDO_FILE)"
+EOF
+
+ [ -z "$${IPKG_INSTROOT}" ] && [ -f "$${DEFAULT_SCRIPT}" ] && sh "$${DEFAULT_SCRIPT}"
+
+ exit 0
+endef
+
+define Package/keepalived-sync/postrm
+ #!/bin/sh
+
+ [ -n "$${IPKG_INSTROOT}" ] && exit 0
+
+ [ -d "$(KEYS_DIR)" ] && rm -rf "$(KEYS_DIR)"
+ [ -d "$(USER_HOME)" ] && rm -rf "$(USER_HOME)"
+ [ -f "$(SUDO_FILE)" ] && rm -f "$(SUDO_FILE)"
+
+ sed -i -e "/^$(USER):/d" /etc/passwd /etc/shadow /etc/group
+
+ exit 0
+endef
+
$(eval $(call BuildPackage,keepalived))
+$(eval $(call BuildPackage,keepalived-sync))
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name rpcd
+
+set_reload_if_sync
+
+add_sync_file /etc/config/rpcd
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name system
+
+set_reload_if_sync
+
+add_sync_file /etc/config/system
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name ucitrack
+
+set_reload_if_sync
+
+add_sync_file /etc/config/ucitrack
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name firewall
+
+set_reload_if_sync
+
+add_sync_file /etc/config/firewall
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name dnsmasq
+
+set_restart_if_master
+set_stop_if_backup
+set_reload_if_sync
+
+add_sync_file /etc/config/dhcp
+add_sync_file /tmp/dhcp.leases
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name dropbear
+
+set_reload_if_backup
+set_reload_if_sync
+
+add_sync_file /etc/config/dropbear
+add_sync_file /etc/dropbear/dropbear_ed25519_host_key
+add_sync_file /etc/dropbear/dropbear_rsa_host_key
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+set_service_name uhttpd
+
+set_restart_if_sync
+
+add_sync_file /etc/config/uhttpd
+add_sync_file /etc/uhttpd.crt
+add_sync_file /etc/uhttpd.key
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+add_sync_file /etc/config/luci
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+add_sync_file /etc/group
+add_sync_file /etc/hosts
+add_sync_file /etc/inittab
+add_sync_file /etc/passwd
+add_sync_file /etc/rc.local
+add_sync_file /etc/profile
+add_sync_file /etc/shadow
+add_sync_file /etc/shell
+add_sync_file /etc/shinit
+add_sync_file /etc/sysctl.conf
+add_sync_file /tmp/dhcp.leases
+
+keepalived_hotplug
--- /dev/null
+#!/bin/sh /etc/rc.common
+
+START=99
+USE_PROCD=1
+PROG="/usr/bin/keepalived-rsync-inotify"
+
+KEEPALIVED_USER=keepalived
+KEEPALIVED_HOME=$(awk -F: "/^$KEEPALIVED_USER/{print \$6}" /etc/passwd)
+
+start_instance() {
+ local cfg=$1
+ local vrrp_instance=$2
+ local peer=$3
+
+ config_get name $cfg name
+ [ -z "$name" ] && return
+
+ [ "$name" != "$peer" ] && return
+
+ config_get sync $cfg sync 0
+ [ "$sync" = "0" ] && return
+
+ config_get sync_mode $cfg sync_mode
+ [ "$sync_mode" != "receive" ] && return
+
+ config_get sync_dir $cfg sync_dir $KEEPALIVED_HOME
+ [ -z "$sync_dir" ] && return
+
+ [ ! -d "$sync_dir" ] && mkdir -m 755 -p "$sync_dir"
+
+ procd_open_instance "$name"
+ procd_set_param command /bin/sh "$PROG" "$vrrp_instance" "$name" "$sync_dir"
+ procd_set_param pidfile /var/run/keepalived-inotify-$name.pid
+ procd_close_instance
+}
+
+process_unicast_peer() {
+ local peer=$1
+ local vrrp_instance=$2
+
+ config_foreach start_instance peer "$vrrp_instance" "$peer"
+}
+
+process_vrrp_instance() {
+ local cfg=$1
+ local peer_instance=$2
+ local name unicast_peer
+
+ config_get name $cfg name
+ config_get unicast_peer $cfg unicast_peer
+
+ if [ -n "$peer_instance" ]; then
+ list_contains unicast_peer "$peer_instance" || return
+ process_unicast_peer "$peer_instance" "$name"
+ else
+ config_list_foreach $cfg unicast_peer process_unicast_peer "$name"
+ fi
+}
+
+start_service() {
+ local peer_instance=$1
+
+ config_load keepalived
+ config_foreach process_vrrp_instance vrrp_instance "$peer_instance"
+}
printf '\n' >> "$KEEPALIVED_CONF"
}
+print_unicast_peer_indent() {
+ local section="$1"
+ local curr_track_elem="$2"
+ local indent="$3"
+ local name address
+
+ config_get name "$section" name
+ [ "$name" != "$curr_track_elem" ] && return 0
+
+ config_get address "$section" address
+ [ -z "$address" ] && return 0
+
+ printf '%b%s\n' "${indent}" "$address">> "$KEEPALIVED_CONF"
+}
+
static_routes() {
local route
config_get route "$1" route
# Handle simple lists of strings (with no spaces in between)
for opt in unicast_peer; do
config_get "$opt" "$1" "$opt"
- print_list_indent "$opt"
+ eval optval=\$$opt
+ [ -z "$optval" ] && continue
+ printf '%b%s {\n' "${INDENT_1}" "$opt" >> "$KEEPALIVED_CONF"
+ for t in $optval; do
+ config_foreach print_unicast_peer_indent peer "$t" "$INDENT_2"
+ done
+ printf '%b}\n' "${INDENT_1}" >> "$KEEPALIVED_CONF"
done
unset optval
--- /dev/null
+#!/bin/sh
+
+# shellcheck disable=SC2039
+
+__FILE__="$(basename "$0")"
+
+KEEPALIVED_USER=keepalived
+KEEPALIVED_DEBUG=0
+
+__function__() {
+ type "$1" > /dev/null 2>&1
+}
+
+log() {
+ local facility=$1
+ shift
+ logger -t "${__FILE__}[$$]" -p "$facility" "$*"
+}
+
+log_info() {
+ log info "$*"
+}
+
+log_debug() {
+ [ "$KEEPALIVED_DEBUG" = "0" ] && return
+ log debug "$*"
+}
+
+log_notice() {
+ log notice "$*"
+}
+
+log_warn() {
+ log warn "$*"
+}
+
+log_err() {
+ log err "$*"
+}
+
+get_rsync_user() {
+ echo "$KEEPALIVED_USER"
+}
+
+get_rsync_user_home() {
+ awk -F: "/^$KEEPALIVED_USER/{print \$6}" /etc/passwd
+}
--- /dev/null
+#!/bin/sh
+
+# shellcheck disable=SC2039
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/common.sh
+
+set_var() {
+ export "$1=$2"
+}
+
+get_var() {
+ eval echo "\"\${${1}}\""
+}
+
+get_var_flag() {
+ local value
+
+ value=$(get_var "$1")
+ value=${value:-0}
+ [ "$value" = "0" ] && return 1
+
+ return 0
+}
+
+_service() {
+ [ -z "$SERVICE_NAME" ] && return
+
+ local rc="/etc/init.d/$SERVICE_NAME"
+
+ [ ! -x "$rc" ] && return
+
+ case $1 in
+ start) $rc running || $rc start ;;
+ stop) $rc running && $rc stop ;;
+ reload)
+ if $rc running; then
+ $rc reload
+ else
+ $rc start
+ fi
+ ;;
+ restart)
+ if $rc running; then
+ $rc restart
+ else
+ $rc start
+ fi
+ ;;
+ esac
+}
+
+_start_service() {
+ _service start
+}
+
+_stop_service() {
+ _service stop
+}
+
+_restart_service() {
+ _service restart
+}
+
+_reload_service() {
+ _service reload
+}
+
+set_service_name() {
+ set_var SERVICE_NAME "$1"
+}
+
+add_sync_file() {
+ append SYNC_FILES_LIST "$1"
+}
+
+is_sync_file() {
+ list_contains SYNC_FILES_LIST "$1"
+}
+
+set_update_target() {
+ set_var UPDATE_TARGET "${1:-1}"
+}
+
+get_update_target() {
+ get_var UPDATE_TARGET
+}
+
+unset_update_target() {
+ set_var UPDATE_TARGET
+}
+
+is_update_target() {
+ get_var_flag UPDATE_TARGET
+}
+
+set_master_cb() {
+ set_var MASTER_CB "$1"
+}
+
+get_master_cb() {
+ get_var MASTER_CB
+}
+
+set_backup_cb() {
+ set_var BACKUP_CB "$1"
+}
+
+get_backup_cb() {
+ get_var BACKUP_CB
+}
+
+set_fault_cb() {
+ set_var FAULT_CB "$1"
+}
+
+get_fault_cb() {
+ get_var FAULT_CB
+}
+
+set_sync_cb() {
+ set_var SYNC_CB "$1"
+}
+
+get_sync_cb() {
+ get_var SYNC_CB
+}
+
+set_reload_if_master() {
+ set_var NOTIFY_MASTER_RELOAD 1
+}
+
+master_and_reload() {
+ get_var_flag NOTIFY_MASTER_RELOAD
+}
+
+set_restart_if_master() {
+ set_var NOTIFY_MASTER_RESTART 1
+}
+
+master_and_restart() {
+ get_var_flag NOTIFY_MASTER_RESTART
+}
+
+set_reload_if_backup() {
+ set_var NOTIFY_BACKUP_RELOAD 1
+}
+
+backup_and_reload() {
+ get_var_flag NOTIFY_BACKUP_RELOAD
+}
+
+set_stop_if_backup() {
+ set_var NOTIFY_BACKUP_STOP 1
+}
+
+backup_and_stop() {
+ get_var_flag NOTIFY_BACKUP_STOP 1
+}
+
+set_reload_if_sync() {
+ set_var NOTIFY_SYNC_RELOAD "${1:-1}"
+}
+
+get_reload_if_sync() {
+ get_var NOTIFY_SYNC_RELOAD
+}
+
+sync_and_reload() {
+ get_var_flag NOTIFY_SYNC_RELOAD
+}
+
+set_restart_if_sync() {
+ set_var NOTIFY_SYNC_RESTART 1
+}
+
+sync_and_restart() {
+ get_var_flag NOTIFY_SYNC_RESTART
+}
+
+_notify_master() {
+ if master_and_reload; then
+ log_debug "reload service $SERVICE_NAME"
+ _reload_service
+ elif master_and_restart; then
+ log_debug "restart service $SERVICE_NAME"
+ _restart_service
+ fi
+}
+
+_notify_backup() {
+ if backup_and_stop; then
+ log_debug "stop service $SERVICE_NAME"
+ _stop_service
+ elif backup_and_reload; then
+ log_debug "restart service $SERVICE_NAME"
+ _restart_service
+ fi
+}
+
+_notify_fault() {
+ return 0
+}
+
+_notify_sync() {
+ [ -z "$RSYNC_SOURCE" ] && return
+ [ -z "$RSYNC_TARGET" ] && return
+
+ if ! is_update_target; then
+ log_notice "skip $RSYNC_TARGET. Update target not set. To set use \"set_update_target 1\""
+ return
+ fi
+
+ is_sync_file "$RSYNC_TARGET" || return
+
+ if ! cp -a "$RSYNC_SOURCE" "$RSYNC_TARGET"; then
+ log_err "can not copy $RSYNC_SOURCE => $RSYNC_TARGET"
+ return
+ fi
+
+ log_debug "updated $RSYNC_SOURCE to $RSYNC_TARGET"
+
+ if sync_and_reload; then
+ log_debug "reload service $SERVICE_NAME"
+ _reload_service
+ elif sync_and_restart; then
+ log_debug "restart service $SERVICE_NAME"
+ _restart_service
+ fi
+}
+
+call_cb() {
+ [ $# -eq 0 ] && return
+ if __function__ "$1"; then
+ log_debug "calling function \"$1\""
+ "$1"
+ else
+ log_err "function \"$1\" not defined"
+ fi
+}
+
+keepalived_hotplug() {
+ [ -z "$(get_master_cb)" ] && set_master_cb _notify_master
+ [ -z "$(get_backup_cb)" ] && set_backup_cb _notify_backup
+ [ -z "$(get_fault_cb)" ] && set_fault_cb _notify_fault
+ [ -z "$(get_sync_cb)" ] && set_sync_cb _notify_sync
+
+ [ -z "$(get_update_target)" ] && set_update_target "$@"
+ [ -z "$(get_reload_if_sync)" ] && set_reload_if_sync "$@"
+
+ case $ACTION in
+ NOTIFY_MASTER) call_cb "$(get_master_cb)" ;;
+ NOTIFY_BACKUP) call_cb "$(get_backup_cb)" ;;
+ NOTIFY_FAULT) call_cb "$(get_fault_cb)" ;;
+ NOTIFY_SYNC) call_cb "$(get_sync_cb)" ;;
+ esac
+}
--- /dev/null
+#!/bin/sh
+
+# shellcheck shell=ash
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/common.sh
+
+if [ $# -lt 3 ]; then
+ echo "$0 <vrrp_instance> <peer> <rsync_dir>"
+ exit 1
+fi
+
+VRRP_INSTANCE=$1
+PEER=$2
+RSYNC_DIR=$3
+
+INOTIFY_ACTIONS="create,delete,modify,move,moved_to,moved_from"
+INOTIFY_PID=""
+TMP_DIR=/tmp/keepalived
+FIFO_FILE="$TMP_DIR"/inotifywait-$PEER.fifo
+
+daemonize_inotifywait() {
+ /usr/bin/inotifywait -q -r --exclude '/\..+' -o "$FIFO_FILE" -m "$RSYNC_DIR" -e ${INOTIFY_ACTIONS} 2> /dev/null &
+ INOTIFY_PID="$!"
+}
+
+main() {
+ local inotify_action inotify_dir inotify_file
+ local source_file target_file
+
+ [ ! -d "$TMP_DIR" ] && mkdir "$TMP_DIR"
+ mkfifo "${FIFO_FILE}" || exit 1
+
+ daemonize_inotifywait
+
+ while read -r inotify_dir inotify_action inotify_file; do
+ source_file="${inotify_dir}${inotify_file}"
+ target_file=$(echo "${inotify_dir}" | sed -e "s:${RSYNC_DIR}::g")"${inotify_file}"
+
+ log_debug "received $target_file ($inotify_action) in $source_file"
+
+ ACTION=NOTIFY_SYNC TYPE=peer NAME=$PEER INSTANCE=$VRRP_INSTANCE \
+ RSYNC_SOURCE="${source_file}" RSYNC_TARGET="${target_file}" \
+ /sbin/hotplug-call keepalived
+ done < "$FIFO_FILE"
+}
+
+TRAP() {
+ [ -n "$INOTIFY_PID" ] && kill "$INOTIFY_PID"
+ [ -e "$FIFO_FILE" ] && rm -f "$FIFO_FILE"
+}
+
+trap TRAP TERM INT
+main "$@"
--- /dev/null
+#!/bin/sh
+
+# shellcheck disable=SC2039
+
+# shellcheck source=/dev/null
+. /usr/share/libubox/jshn.sh
+# shellcheck source=/dev/null
+. /lib/functions.sh
+
+peer() {
+ local cfg=$1
+ local c_name=$2
+ local name last_sync_time last_sync_status
+
+ config_get name "$cfg" name
+ [ "$name" != "$c_name" ] && return
+
+ config_get last_sync_time "$cfg" last_sync_time 0
+ config_get last_sync_status "$cfg" last_sync_status NA
+
+ json_add_object unicast_peer
+ json_add_string name "$name"
+ json_add_int time "$last_sync_time"
+ json_add_string status "$last_sync_status"
+ json_close_array
+}
+
+unicast_peer() {
+ config_foreach peer peer "$1"
+}
+
+vrrp_instance() {
+ local cfg=$1
+ local name
+
+ config_get name "$cfg" name
+
+ json_add_object vrrp_instance
+ json_add_string name "$name"
+ json_add_array unicast_peer
+ config_list_foreach "$cfg" unicast_peer unicast_peer
+ json_close_array
+ json_close_object
+}
+
+rsync_status() {
+ config_load keepalived
+
+ json_init
+ json_add_array vrrp_instance
+ config_foreach vrrp_instance vrrp_instance
+ json_close_array
+ json_dump
+}
+
+sync_help() {
+ json_add_object rsync_status
+ json_close_object
+}
#!/bin/sh
+# shellcheck disable=SC2039
+
+# shellcheck source=/dev/null
. /lib/functions.sh
+# shellcheck source=/dev/null
. /usr/share/libubox/jshn.sh
RPC_SCRIPTS=/usr/libexec/keepalived/rpc
[ ! -d $RPC_SCRIPTS ] && return
- for file in $RPC_SCRIPTS/*; do
+ for file in "$RPC_SCRIPTS"/*; do
obj="${file##*/}"
$1 "${obj%%.*}"
done
}
keepalived_dump() {
- local stats_file="/tmp/keepalived.json"
- local pids
+ local stats_file pids
+
+ stats_file="/tmp/keepalived.json"
[ -f "$stats_file" ] && rm -f "$stats_file"
pids=$(pidof /usr/sbin/keepalived)
if [ -n "$pids" ]; then
- kill -37 $pids > /dev/null 2>&1
+ kill -37 "$pids" > /dev/null 2>&1
json_load "{ \"status\" : $(cat $stats_file) }"
else
json_init
}
call_method() {
- case "$1" in
+ local cmd=$1
+
+ case "$cmd" in
dump)
keepalived_dump
;;
*)
- call_extra $1
+ call_extra "$cmd"
;;
esac
}
list_extra() {
- if __function__ "${1}_help"; then
- ${1}_help
+ local arg func
+
+ arg=$1
+ func="${arg}_help"
+
+ if __function__ "$func"; then
+ $func
else
- json_add_object "$1"
+ json_add_object "$arg"
json_close_object
fi
}
json_add_object dump
json_close_object
- foreach_extra list_extra ${1}
+ foreach_extra list_extra "${1}"
json_dump
}
-main () {
- case "$1" in
+main() {
+ local cmd=$1
+ shift
+
+ case "$cmd" in
list)
- list_methods
+ list_methods "$@"
;;
call)
- call_method $2
+ call_method "$@"
;;
esac
}
--- /dev/null
+#!/bin/sh
+
+# shellcheck disable=SC2039
+
+# shellcheck source=/dev/null
+. /lib/functions.sh
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/common.sh
+
+RSYNC_USER=$(get_rsync_user)
+RSYNC_HOME=$(get_rsync_user_home)
+
+utc_timestamp() {
+ date -u +%s
+}
+
+update_last_sync_time() {
+ uci_revert_state keepalived "$1" last_sync_time
+ uci_set_state keepalived "$1" last_sync_time "$(utc_timestamp)"
+}
+
+update_last_sync_status() {
+ local cfg="$1"
+ shift
+ local status="$*"
+
+ uci_revert_state keepalived "$cfg" last_sync_status
+ uci_set_state keepalived "$cfg" last_sync_status "$status"
+}
+
+ha_sync_send() {
+ local cfg=$1
+ local address ssh_key ssh_port sync_list sync_dir sync_file count
+ local ssh_options ssh_remote dirs_list files_list
+ local changelog="/tmp/changelog"
+
+ config_get address "$cfg" address
+ [ -z "$address" ] && return 0
+
+ config_get ssh_port "$cfg" ssh_port 22
+ config_get sync_dir "$cfg" sync_dir "$RSYNC_HOME"
+ [ -z "$sync_dir" ] && return 0
+ config_get ssh_key "$cfg" ssh_key "$sync_dir"/.ssh/id_rsa
+ config_get sync_list "$cfg" sync_list
+
+ for sync_file in $sync_list $(sysupgrade -l); do
+ [ -f "$sync_file" ] && {
+ dir="${sync_file%/*}"
+ list_contains files_list "${sync_file}" || append files_list "${sync_file}"
+ }
+ [ -d "$sync_file" ] && dir="${sync_file}"
+ list_contains dirs_list "${sync_dir}${dir}" || append dirs_list "${sync_dir}${dir}"
+ done
+
+ ssh_options="-y -y -i $ssh_key -p $ssh_port"
+ ssh_remote="$RSYNC_USER@$address"
+
+ # shellcheck disable=SC2086
+ timeout 10 ssh $ssh_options $ssh_remote mkdir -m 755 -p "$dirs_list /tmp" || {
+ log_err "can not connect to $address. check key or connection"
+ update_last_sync_time "$cfg"
+ update_last_sync_status "$cfg" "SSH Connection Failed"
+ return 0
+ }
+
+ # shellcheck disable=SC2086
+ if rsync --out-format='%n' --dry-run -a --relative ${files_list} -e "ssh $ssh_options" --rsync-path="sudo rsync" "$ssh_remote":"$sync_dir" > "$changelog"; then
+ count=$(wc -l "$changelog")
+ if [ "${count%% *}" = "0" ]; then
+ log_debug "all files are up to date"
+ update_last_sync_time "$cfg"
+ update_last_sync_status "$cfg" "Up to Date"
+ return 0
+ fi
+ else
+ log_err "rsync dry run failed for $address"
+ update_last_sync_time "$cfg"
+ update_last_sync_status "$cfg" "Rsync Detection Failed"
+ return 0
+ fi
+
+ # shellcheck disable=SC2086
+ rsync -a --relative ${files_list} ${changelog} -e "ssh $ssh_options" --rsync-path="sudo rsync" "$ssh_remote":"$sync_dir" || {
+ log_err "rsync transfer failed for $address"
+ update_last_sync_time "$cfg"
+ update_last_sync_status "$cfg" "Rsync Transfer Failed"
+ }
+
+ log_info "keepalived sync is compeleted for $address"
+ update_last_sync_time "$cfg"
+ update_last_sync_status "$cfg" "Successful"
+}
+
+ha_sync_receive() {
+ local cfg=$1
+ local ssh_pubkey
+ local name auth_file home_dir
+
+ config_get name "$cfg" name
+ config_get sync_dir "$cfg" sync_dir "$RSYNC_HOME"
+ [ -z "$sync_dir" ] && return 0
+ config_get ssh_pubkey "$cfg" ssh_pubkey
+ [ -z "$ssh_pubkey" ] && return 0
+
+ home_dir=$sync_dir
+ auth_file="$home_dir/.ssh/authorized_keys"
+
+ if ! grep -q "^$ssh_pubkey$" "$auth_file" 2> /dev/null; then
+ log_notice "public key not found. Updating"
+ echo "$ssh_pubkey" > "$auth_file"
+ chown "$RSYNC_USER":"$RSYNC_USER" "$auth_file"
+ fi
+
+ /etc/init.d/keepalived-inotify enabled || /etc/init.d/keepalived-inotify enable
+ /etc/init.d/keepalived-inotify running "$name" || /etc/init.d/keepalived-inotify start "$name"
+}
+
+ha_sync_each_peer() {
+ local cfg="$1"
+ local c_name="$2"
+ local name sync sync_mode
+
+ config_get name "$cfg" name
+ [ "$name" != "$c_name" ] && return 0
+
+ config_get sync "$cfg" sync 0
+ [ "$sync" = "0" ] && return 0
+
+ config_get sync_mode "$cfg" sync_mode
+ [ -z "$sync_mode" ] && return 0
+
+ case "$sync_mode" in
+ send) ha_sync_send "$cfg" ;;
+ receive) ha_sync_receive "$cfg" ;;
+ esac
+}
+
+ha_sync_peers() {
+ config_foreach ha_sync_each_peer peer "$1"
+}
+
+ha_sync() {
+ config_list_foreach "$1" unicast_peer ha_sync_peers
+}
+
+main() {
+ local lockfile="/var/lock/keepalived-rsync.lock"
+
+ if ! lock -n "$lockfile" > /dev/null 2>&1; then
+ log_info "another process is already running"
+ return 1
+ fi
+
+ config_load keepalived
+ config_foreach ha_sync vrrp_instance
+
+ lock -u "$lockfile"
+
+ return 0
+}
+
+main "$@"