Merge pull request #19374 from jempatel/improve_keepalived-uci-sync
keepalived: high-availability files and data sync
This commit is contained in:
commit
05b0d3fc12
18 changed files with 912 additions and 15 deletions
|
@ -274,4 +274,103 @@ endif
|
||||||
|
|
||||||
endef
|
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))
|
||||||
|
$(eval $(call BuildPackage,keepalived-sync))
|
||||||
|
|
12
net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd
Normal file
12
net/keepalived/files/etc/hotplug.d/keepalived/501-rpcd
Normal file
|
@ -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
|
12
net/keepalived/files/etc/hotplug.d/keepalived/505-system
Normal file
12
net/keepalived/files/etc/hotplug.d/keepalived/505-system
Normal file
|
@ -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
|
12
net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack
Normal file
12
net/keepalived/files/etc/hotplug.d/keepalived/509-ucitrack
Normal file
|
@ -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
|
12
net/keepalived/files/etc/hotplug.d/keepalived/511-firewall
Normal file
12
net/keepalived/files/etc/hotplug.d/keepalived/511-firewall
Normal file
|
@ -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
|
15
net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq
Normal file
15
net/keepalived/files/etc/hotplug.d/keepalived/551-dnsmasq
Normal file
|
@ -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
|
15
net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear
Normal file
15
net/keepalived/files/etc/hotplug.d/keepalived/555-dropbear
Normal file
|
@ -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
|
14
net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd
Normal file
14
net/keepalived/files/etc/hotplug.d/keepalived/600-uhttpd
Normal file
|
@ -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
|
8
net/keepalived/files/etc/hotplug.d/keepalived/700-luci
Normal file
8
net/keepalived/files/etc/hotplug.d/keepalived/700-luci
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. /lib/functions/keepalived/hotplug.sh
|
||||||
|
|
||||||
|
add_sync_file /etc/config/luci
|
||||||
|
|
||||||
|
keepalived_hotplug
|
18
net/keepalived/files/etc/hotplug.d/keepalived/810-files
Normal file
18
net/keepalived/files/etc/hotplug.d/keepalived/810-files
Normal file
|
@ -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
|
65
net/keepalived/files/etc/init.d/keepalived-inotify
Normal file
65
net/keepalived/files/etc/init.d/keepalived-inotify
Normal file
|
@ -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"
|
||||||
|
}
|
|
@ -256,6 +256,21 @@ print_track_bfd_indent() {
|
||||||
printf '\n' >> "$KEEPALIVED_CONF"
|
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() {
|
static_routes() {
|
||||||
local route
|
local route
|
||||||
config_get route "$1" route
|
config_get route "$1" route
|
||||||
|
@ -403,7 +418,13 @@ vrrp_instance() {
|
||||||
# Handle simple lists of strings (with no spaces in between)
|
# Handle simple lists of strings (with no spaces in between)
|
||||||
for opt in unicast_peer; do
|
for opt in unicast_peer; do
|
||||||
config_get "$opt" "$1" "$opt"
|
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
|
done
|
||||||
unset optval
|
unset optval
|
||||||
|
|
||||||
|
|
47
net/keepalived/files/lib/functions/keepalived/common.sh
Normal file
47
net/keepalived/files/lib/functions/keepalived/common.sh
Normal file
|
@ -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
|
||||||
|
}
|
257
net/keepalived/files/lib/functions/keepalived/hotplug.sh
Normal file
257
net/keepalived/files/lib/functions/keepalived/hotplug.sh
Normal file
|
@ -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
|
||||||
|
}
|
54
net/keepalived/files/usr/bin/keepalived-rsync-inotify
Normal file
54
net/keepalived/files/usr/bin/keepalived-rsync-inotify
Normal file
|
@ -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 "$@"
|
59
net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh
Normal file
59
net/keepalived/files/usr/libexec/keepalived/rpc/sync.sh
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
# shellcheck disable=SC2039
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
. /lib/functions.sh
|
. /lib/functions.sh
|
||||||
|
# shellcheck source=/dev/null
|
||||||
. /usr/share/libubox/jshn.sh
|
. /usr/share/libubox/jshn.sh
|
||||||
|
|
||||||
RPC_SCRIPTS=/usr/libexec/keepalived/rpc
|
RPC_SCRIPTS=/usr/libexec/keepalived/rpc
|
||||||
|
@ -16,21 +20,22 @@ foreach_extra() {
|
||||||
|
|
||||||
[ ! -d $RPC_SCRIPTS ] && return
|
[ ! -d $RPC_SCRIPTS ] && return
|
||||||
|
|
||||||
for file in $RPC_SCRIPTS/*; do
|
for file in "$RPC_SCRIPTS"/*; do
|
||||||
obj="${file##*/}"
|
obj="${file##*/}"
|
||||||
$1 "${obj%%.*}"
|
$1 "${obj%%.*}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
keepalived_dump() {
|
keepalived_dump() {
|
||||||
local stats_file="/tmp/keepalived.json"
|
local stats_file pids
|
||||||
local pids
|
|
||||||
|
stats_file="/tmp/keepalived.json"
|
||||||
|
|
||||||
[ -f "$stats_file" ] && rm -f "$stats_file"
|
[ -f "$stats_file" ] && rm -f "$stats_file"
|
||||||
|
|
||||||
pids=$(pidof /usr/sbin/keepalived)
|
pids=$(pidof /usr/sbin/keepalived)
|
||||||
if [ -n "$pids" ]; then
|
if [ -n "$pids" ]; then
|
||||||
kill -37 $pids > /dev/null 2>&1
|
kill -37 "$pids" > /dev/null 2>&1
|
||||||
json_load "{ \"status\" : $(cat $stats_file) }"
|
json_load "{ \"status\" : $(cat $stats_file) }"
|
||||||
else
|
else
|
||||||
json_init
|
json_init
|
||||||
|
@ -50,21 +55,28 @@ call_extra() {
|
||||||
}
|
}
|
||||||
|
|
||||||
call_method() {
|
call_method() {
|
||||||
case "$1" in
|
local cmd=$1
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
dump)
|
dump)
|
||||||
keepalived_dump
|
keepalived_dump
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
call_extra $1
|
call_extra "$cmd"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
list_extra() {
|
list_extra() {
|
||||||
if __function__ "${1}_help"; then
|
local arg func
|
||||||
${1}_help
|
|
||||||
|
arg=$1
|
||||||
|
func="${arg}_help"
|
||||||
|
|
||||||
|
if __function__ "$func"; then
|
||||||
|
$func
|
||||||
else
|
else
|
||||||
json_add_object "$1"
|
json_add_object "$arg"
|
||||||
json_close_object
|
json_close_object
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
@ -77,18 +89,21 @@ list_methods() {
|
||||||
json_add_object dump
|
json_add_object dump
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
foreach_extra list_extra ${1}
|
foreach_extra list_extra "${1}"
|
||||||
|
|
||||||
json_dump
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
main () {
|
main() {
|
||||||
case "$1" in
|
local cmd=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$cmd" in
|
||||||
list)
|
list)
|
||||||
list_methods
|
list_methods "$@"
|
||||||
;;
|
;;
|
||||||
call)
|
call)
|
||||||
call_method $2
|
call_method "$@"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
162
net/keepalived/files/usr/share/keepalived/scripts/rsync.sh
Normal file
162
net/keepalived/files/usr/share/keepalived/scripts/rsync.sh
Normal file
|
@ -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 "$@"
|
Loading…
Reference in a new issue