diff --git a/net/keepalived/Makefile b/net/keepalived/Makefile index 2e782e81a..d4f43b006 100644 --- a/net/keepalived/Makefile +++ b/net/keepalived/Makefile @@ -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 index 000000000..092eb057f --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd @@ -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 index 000000000..147a2257a --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/505-system @@ -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 index 000000000..bacbf2597 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack @@ -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 index 000000000..d8619e9f1 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/511-firewall @@ -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 index 000000000..a9b3c79e3 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq @@ -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 index 000000000..5ebe0aa91 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear @@ -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 index 000000000..b5fcdba82 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd @@ -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 index 000000000..3bf7f10dd --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/700-luci @@ -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 index 000000000..d6f9b90b7 --- /dev/null +++ b/net/keepalived/files/etc/hotplug.d/keepalived/810-files @@ -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 index 000000000..646c04b29 --- /dev/null +++ b/net/keepalived/files/etc/init.d/keepalived-inotify @@ -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" +} diff --git a/net/keepalived/files/keepalived.init b/net/keepalived/files/keepalived.init index 39e9f4742..b13f10c40 100644 --- a/net/keepalived/files/keepalived.init +++ b/net/keepalived/files/keepalived.init @@ -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 index 000000000..bca57a214 --- /dev/null +++ b/net/keepalived/files/lib/functions/keepalived/common.sh @@ -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 index 000000000..8691872c6 --- /dev/null +++ b/net/keepalived/files/lib/functions/keepalived/hotplug.sh @@ -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 index 000000000..226f928a9 --- /dev/null +++ b/net/keepalived/files/usr/bin/keepalived-rsync-inotify @@ -0,0 +1,54 @@ +#!/bin/sh + +# shellcheck shell=ash + +# shellcheck source=/dev/null +. /lib/functions/keepalived/common.sh + +if [ $# -lt 3 ]; then + echo "$0 " + 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 index 000000000..c5dadf32a --- /dev/null +++ b/net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh @@ -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 +} diff --git a/net/keepalived/files/usr/libexec/rpcd/keepalived b/net/keepalived/files/usr/libexec/rpcd/keepalived index 2ae14f105..fd2e20c20 100644 --- a/net/keepalived/files/usr/libexec/rpcd/keepalived +++ b/net/keepalived/files/usr/libexec/rpcd/keepalived @@ -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 index 000000000..fa15df211 --- /dev/null +++ b/net/keepalived/files/usr/share/keepalived/scripts/rsync.sh @@ -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 "$@"