keepalived: high-availability files and data sync
authorJaymin Patel <jem.patel@gmail.com>
Fri, 9 Sep 2022 13:40:49 +0000 (19:10 +0530)
committerJaymin Patel <jem.patel@gmail.com>
Thu, 13 Oct 2022 11:27:02 +0000 (16:57 +0530)
add new package keepalived-sync to synchronize files and data
between master and backup node. The master node uses SSH over rsync
to send and the backup node will use inotifywatch to watch received files.

The master node can track rsync.sh script to send configuration file on
a backup node based on the vrrp_script configuration of the same script.

The backup node will have a keepalived-inotify service, which would watch
for newly received files and it would call hotplug event. Each service
can keep its respective script under the keepalived hotplug directory and
executes commands to stop, start service or update any config in real-time.

Whenever a switchover will happen, the backup node would have the latest
config and data files from the master node.

Hotplug events can be used to apply config when files are received.

Signed-off-by: Jaymin Patel <jem.patel@gmail.com>
18 files changed:
net/keepalived/Makefile
net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/505-system [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/511-firewall [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/700-luci [new file with mode: 0644]
net/keepalived/files/etc/hotplug.d/keepalived/810-files [new file with mode: 0644]
net/keepalived/files/etc/init.d/keepalived-inotify [new file with mode: 0644]
net/keepalived/files/keepalived.init
net/keepalived/files/lib/functions/keepalived/common.sh [new file with mode: 0644]
net/keepalived/files/lib/functions/keepalived/hotplug.sh [new file with mode: 0644]
net/keepalived/files/usr/bin/keepalived-rsync-inotify [new file with mode: 0644]
net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh [new file with mode: 0644]
net/keepalived/files/usr/libexec/rpcd/keepalived
net/keepalived/files/usr/share/keepalived/scripts/rsync.sh [new file with mode: 0644]

index 2e782e81a68dce0d7befa146ecf9c468b20b8092..d4f43b0062b99aa68b35c50040d84f06a3f22e8b 100644 (file)
@@ -274,4 +274,103 @@ endif
 
 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))
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd b/net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd
new file mode 100644 (file)
index 0000000..092eb05
--- /dev/null
@@ -0,0 +1,12 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/505-system b/net/keepalived/files/etc/hotplug.d/keepalived/505-system
new file mode 100644 (file)
index 0000000..147a225
--- /dev/null
@@ -0,0 +1,12 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack b/net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack
new file mode 100644 (file)
index 0000000..bacbf25
--- /dev/null
@@ -0,0 +1,12 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/511-firewall b/net/keepalived/files/etc/hotplug.d/keepalived/511-firewall
new file mode 100644 (file)
index 0000000..d8619e9
--- /dev/null
@@ -0,0 +1,12 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq b/net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq
new file mode 100644 (file)
index 0000000..a9b3c79
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear b/net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear
new file mode 100644 (file)
index 0000000..5ebe0aa
--- /dev/null
@@ -0,0 +1,15 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd b/net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd
new file mode 100644 (file)
index 0000000..b5fcdba
--- /dev/null
@@ -0,0 +1,14 @@
+#!/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
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/700-luci b/net/keepalived/files/etc/hotplug.d/keepalived/700-luci
new file mode 100644 (file)
index 0000000..3bf7f10
--- /dev/null
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# shellcheck source=/dev/null
+. /lib/functions/keepalived/hotplug.sh
+
+add_sync_file /etc/config/luci
+
+keepalived_hotplug
diff --git a/net/keepalived/files/etc/hotplug.d/keepalived/810-files b/net/keepalived/files/etc/hotplug.d/keepalived/810-files
new file mode 100644 (file)
index 0000000..d6f9b90
--- /dev/null
@@ -0,0 +1,18 @@
+#!/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
diff --git a/net/keepalived/files/etc/init.d/keepalived-inotify b/net/keepalived/files/etc/init.d/keepalived-inotify
new file mode 100644 (file)
index 0000000..646c04b
--- /dev/null
@@ -0,0 +1,65 @@
+#!/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"
+}
index 39e9f474266969ee329f06cb2f8c29f9757e70c0..b13f10c40e472e023d02e6f150a8d66ecd6210a2 100644 (file)
@@ -256,6 +256,21 @@ print_track_bfd_indent() {
        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
@@ -403,7 +418,13 @@ vrrp_instance() {
        # 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
 
diff --git a/net/keepalived/files/lib/functions/keepalived/common.sh b/net/keepalived/files/lib/functions/keepalived/common.sh
new file mode 100644 (file)
index 0000000..bca57a2
--- /dev/null
@@ -0,0 +1,47 @@
+#!/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
+}
diff --git a/net/keepalived/files/lib/functions/keepalived/hotplug.sh b/net/keepalived/files/lib/functions/keepalived/hotplug.sh
new file mode 100644 (file)
index 0000000..8691872
--- /dev/null
@@ -0,0 +1,257 @@
+#!/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
+}
diff --git a/net/keepalived/files/usr/bin/keepalived-rsync-inotify b/net/keepalived/files/usr/bin/keepalived-rsync-inotify
new file mode 100644 (file)
index 0000000..226f928
--- /dev/null
@@ -0,0 +1,54 @@
+#!/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 "$@"
diff --git a/net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh b/net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh
new file mode 100644 (file)
index 0000000..c5dadf3
--- /dev/null
@@ -0,0 +1,59 @@
+#!/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
+}
index 2ae14f1054bc8897b910e735affcb8a102f52ccd..fd2e20c20066b0902c83879f1e92eb4ab49bcd5a 100644 (file)
@@ -1,6 +1,10 @@
 #!/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
@@ -16,21 +20,22 @@ foreach_extra() {
 
        [ ! -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
@@ -50,21 +55,28 @@ call_extra() {
 }
 
 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
 }
@@ -77,18 +89,21 @@ list_methods() {
        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
 }
diff --git a/net/keepalived/files/usr/share/keepalived/scripts/rsync.sh b/net/keepalived/files/usr/share/keepalived/scripts/rsync.sh
new file mode 100644 (file)
index 0000000..fa15df2
--- /dev/null
@@ -0,0 +1,162 @@
+#!/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 "$@"