Prior to this change, 'true=emqx:is_running().' Erlang assertion is evaluated before 'emqx start' command gives up waiting. The Erlang assertion would result in a error level crash log which can be confusing. This commit changes the equals to 'true' check to bash script instead. Also deleted the log message which advised users to report an issue in case timeout happens, because daemon mode is almost only used in tests
1313 lines
43 KiB
Bash
Executable File
1313 lines
43 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# -*- tab-width:4;indent-tabs-mode:nil -*-
|
|
# ex: ts=4 sw=4 et
|
|
|
|
set -euo pipefail
|
|
|
|
DEBUG="${DEBUG:-0}"
|
|
if [ "$DEBUG" -eq 1 ]; then
|
|
set -x
|
|
fi
|
|
if [ "$DEBUG" -eq 2 ]; then
|
|
set -x
|
|
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
|
|
fi
|
|
|
|
# We need to find real directory with emqx files on all platforms
|
|
# even when bin/emqx is symlinked on several levels
|
|
# - readlink -f works perfectly, but `-f` flag has completely different meaning in BSD version,
|
|
# so we can't use it universally.
|
|
# - `stat -f%R` on MacOS does exactly what `readlink -f` does on Linux, but we can't use it
|
|
# as a universal solution either because GNU stat has different syntax and this argument is invalid.
|
|
# Also, version of stat which supports this syntax is only available since MacOS 12
|
|
if [ "$(uname -s)" == 'Darwin' ]; then
|
|
product_version="$(sw_vers -productVersion | cut -d '.' -f 1)"
|
|
if [ "$product_version" -ge 12 ]; then
|
|
# if homebrew coreutils package is installed, GNU version of stat can take precedence,
|
|
# so we use absolute path to ensure we are calling MacOS default
|
|
RUNNER_ROOT_DIR="$(cd "$(dirname "$(/usr/bin/stat -f%R "$0" || echo "$0")")"/..; pwd -P)"
|
|
else
|
|
# try our best to resolve link on MacOS <= 11
|
|
RUNNER_ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
|
|
fi
|
|
else
|
|
RUNNER_ROOT_DIR="$(cd "$(dirname "$(realpath "$0" || echo "$0")")"/..; pwd -P)"
|
|
fi
|
|
|
|
# shellcheck disable=SC1090,SC1091
|
|
. "$RUNNER_ROOT_DIR"/releases/emqx_vars
|
|
|
|
# defined in emqx_vars
|
|
export RUNNER_ROOT_DIR
|
|
export EMQX_ETC_DIR
|
|
export REL_VSN
|
|
export SCHEMA_MOD
|
|
export IS_ENTERPRISE
|
|
|
|
RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
|
|
CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
|
|
REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN"
|
|
|
|
WHOAMI=$(whoami 2>/dev/null || id -u)
|
|
|
|
# hocon try to read environment variables starting with "EMQX_"
|
|
export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
|
|
|
|
export ERTS_DIR="$RUNNER_ROOT_DIR/erts-$ERTS_VSN"
|
|
export BINDIR="$ERTS_DIR/bin"
|
|
export EMU="beam"
|
|
export PROGNAME="erl"
|
|
export ERTS_LIB_DIR="$RUNNER_ROOT_DIR/lib"
|
|
DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs"
|
|
|
|
logerr() {
|
|
if [ "${TERM:-dumb}" = dumb ]; then
|
|
echo -e "ERROR: $*" 1>&2
|
|
else
|
|
echo -e "$(tput setaf 1)ERROR: $*$(tput sgr0)" 1>&2
|
|
fi
|
|
}
|
|
|
|
logwarn() {
|
|
if [ "${TERM:-dumb}" = dumb ]; then
|
|
echo "WARNING: $*"
|
|
else
|
|
echo "$(tput setaf 3)WARNING: $*$(tput sgr0)"
|
|
fi
|
|
}
|
|
|
|
logdebug() {
|
|
if [ "$DEBUG" -eq 1 ]; then
|
|
echo "DEBUG: $*"
|
|
fi
|
|
}
|
|
|
|
die() {
|
|
set +x
|
|
logerr "$1"
|
|
errno=${2:-1}
|
|
exit "$errno"
|
|
}
|
|
|
|
assert_node_alive() {
|
|
if ! relx_nodetool "ping" > /dev/null; then
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
local command="$1"
|
|
|
|
case "$command" in
|
|
start)
|
|
echo "Start EMQX service in daemon mode"
|
|
;;
|
|
stop)
|
|
echo "Stop the running EMQX program"
|
|
;;
|
|
console)
|
|
echo "Boot up EMQX service in an interactive Erlang or Elixir shell"
|
|
echo "This command needs a tty"
|
|
;;
|
|
console_clean)
|
|
echo "This command does NOT boot up the EMQX service"
|
|
echo "It only starts an interactive Erlang or Elixir console with all the"
|
|
echo "EMQX code available"
|
|
;;
|
|
foreground)
|
|
echo "Start EMQX in foreground mode without an interactive shell"
|
|
;;
|
|
pid)
|
|
echo "Print out EMQX process identifier"
|
|
;;
|
|
ping)
|
|
echo "Check if the EMQX node is up and running"
|
|
echo "This command exit with 0 silently if node is running"
|
|
;;
|
|
escript)
|
|
echo "Execute a escript using the Erlang runtime from EMQX package installation"
|
|
echo "For example $REL_NAME escript /path/to/my/escript my_arg1 my_arg2"
|
|
;;
|
|
attach)
|
|
echo "This command is applicable when EMQX is started in daemon mode."
|
|
echo "It attaches the current shell to EMQX's control console"
|
|
echo "through a named pipe."
|
|
logwarn "try to use the safer alternative, remote_console command."
|
|
;;
|
|
remote_console)
|
|
echo "Start an interactive shell running an Erlang or Elixir node which "
|
|
echo "hidden-connects to the running EMQX node".
|
|
echo "This command is mostly used for troubleshooting."
|
|
;;
|
|
ertspath)
|
|
echo "Print path to Erlang runtime bin dir"
|
|
;;
|
|
rpc)
|
|
echo "Usage: $REL_NAME rpc MODULE FUNCTION [ARGS, ...]"
|
|
echo "Connect to the EMQX node and make an Erlang RPC"
|
|
echo "This command blocks for at most 60 seconds."
|
|
echo "It exits with non-zero code in case of any RPC failure"
|
|
echo "including connection error and runtime exception"
|
|
;;
|
|
rpcterms)
|
|
echo "Usage: $REL_NAME rpcterms MODULE FUNCTION [ARGS, ...]"
|
|
echo "Connect to the EMQX node and make an Erlang RPC"
|
|
echo "The result of the RPC call is pretty-printed as an "
|
|
echo "Erlang term"
|
|
;;
|
|
root_dir)
|
|
echo "Print EMQX installation root dir"
|
|
;;
|
|
eval)
|
|
echo "Evaluate an Erlang expression in the EMQX node."
|
|
;;
|
|
eval-ex)
|
|
echo "Evaluate an Elixir expression in the EMQX node. Only applies to Elixir node"
|
|
;;
|
|
versions)
|
|
echo "List installed EMQX release versions and their status"
|
|
;;
|
|
unpack)
|
|
echo "Usage: $REL_NAME unpack [VERSION]"
|
|
echo "Unpacks a release package VERSION, it assumes that this"
|
|
echo "release package tarball has already been deployed at one"
|
|
echo "of the following locations:"
|
|
echo " releases/<relname>-<version>.tar.gz"
|
|
;;
|
|
install)
|
|
echo "Usage: $REL_NAME install [VERSION]"
|
|
echo "Installs a release package VERSION, it assumes that this"
|
|
echo "release package tarball has already been deployed at one"
|
|
echo "of the following locations:"
|
|
echo " releases/<relname>-<version>.tar.gz"
|
|
echo ""
|
|
echo " --no-permanent Install release package VERSION but"
|
|
echo " don't make it permanent"
|
|
;;
|
|
uninstall)
|
|
echo "Usage: $REL_NAME uninstall [VERSION]"
|
|
echo "Uninstalls a release VERSION, it will only accept"
|
|
echo "versions that are not currently in use"
|
|
;;
|
|
upgrade)
|
|
echo "Usage: $REL_NAME upgrade [VERSION]"
|
|
echo "Upgrades the currently running release to VERSION, it assumes"
|
|
echo "that a release package tarball has already been deployed at one"
|
|
echo "of the following locations:"
|
|
echo " releases/<relname>-<version>.tar.gz"
|
|
echo ""
|
|
echo " --no-permanent Install release package VERSION but"
|
|
echo " don't make it permanent"
|
|
;;
|
|
downgrade)
|
|
echo "Usage: $REL_NAME downgrade [VERSION]"
|
|
echo "Downgrades the currently running release to VERSION, it assumes"
|
|
echo "that a release package tarball has already been deployed at one"
|
|
echo "of the following locations:"
|
|
echo " releases/<relname>-<version>.tar.gz"
|
|
echo ""
|
|
echo " --no-permanent Install release package VERSION but"
|
|
echo " don't make it permanent"
|
|
;;
|
|
check_config)
|
|
echo "Checks the EMQX config without generating any files"
|
|
;;
|
|
*)
|
|
echo "Usage: $REL_NAME COMMAND [help]"
|
|
echo ''
|
|
echo "Commonly used COMMANDs:"
|
|
echo " start: Start EMQX in daemon mode"
|
|
echo " console: Start EMQX in an interactive Erlang or Elixir shell"
|
|
echo " foreground: Start EMQX in foreground mode without an interactive shell"
|
|
echo " stop: Stop the running EMQX node"
|
|
echo " ctl: Administration commands, execute '$REL_NAME ctl help' for more details"
|
|
echo ''
|
|
echo "More:"
|
|
echo " Shell attach: remote_console | attach"
|
|
# echo " Up/Down-grade: upgrade | downgrade | install | uninstall | versions" # TODO enable when supported
|
|
echo " Install Info: ertspath | root_dir"
|
|
echo " Runtime Status: pid | ping"
|
|
echo " Validate Config: check_config"
|
|
echo " Advanced: console_clean | escript | rpc | rpcterms | eval | eval-ex"
|
|
echo ''
|
|
echo "Execute '$REL_NAME COMMAND help' for more information"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
COMMAND="${1:-}"
|
|
GREP='grep --color=never'
|
|
|
|
if [ -z "$COMMAND" ]; then
|
|
usage 'help'
|
|
exit 1
|
|
elif [ "$COMMAND" = 'help' ]; then
|
|
usage 'help'
|
|
exit 0
|
|
fi
|
|
|
|
if [ "${2:-}" = 'help' ]; then
|
|
## 'ctl' command has its own usage info
|
|
if [ "$COMMAND" != 'ctl' ]; then
|
|
usage "$COMMAND"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
## IS_BOOT_COMMAND is set for later to inspect node name and cookie from hocon config (or env variable)
|
|
case "${COMMAND}" in
|
|
start|console|console_clean|foreground|check_config)
|
|
IS_BOOT_COMMAND='yes'
|
|
;;
|
|
ertspath)
|
|
echo "$ERTS_DIR"
|
|
exit 0
|
|
;;
|
|
root_dir)
|
|
echo "$RUNNER_ROOT_DIR"
|
|
exit 0
|
|
;;
|
|
*)
|
|
IS_BOOT_COMMAND='no'
|
|
;;
|
|
esac
|
|
|
|
## backward compatible
|
|
if [ -d "$ERTS_DIR/lib" ]; then
|
|
export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
|
|
fi
|
|
|
|
# Simple way to check the correct user and fail early
|
|
check_user() {
|
|
# Validate that the user running the script is the owner of the
|
|
# RUN_DIR.
|
|
if [ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]; then
|
|
if [ "x$WHOAMI" != "xroot" ]; then
|
|
echo "You need to be root or use sudo to run this command"
|
|
exit 1
|
|
fi
|
|
CMD="DEBUG=$DEBUG \"$RUNNER_SCRIPT\" "
|
|
for ARG in "$@"; do
|
|
CMD="${CMD} \"$ARG\""
|
|
done
|
|
# This will drop privileges into the runner user
|
|
# It exec's in a new shell and the current shell will exit
|
|
exec su - "$RUNNER_USER" -c "$CMD"
|
|
fi
|
|
}
|
|
|
|
# Make sure the user running this script is the owner and/or su to that user
|
|
check_user "$@"
|
|
ES=$?
|
|
if [ "$ES" -ne 0 ]; then
|
|
exit $ES
|
|
fi
|
|
|
|
# Make sure log directory exists
|
|
mkdir -p "$EMQX_LOG_DIR"
|
|
|
|
# turn off debug as this is static
|
|
set +x
|
|
COMPATIBILITY_CHECK='
|
|
io:format("BEAM_OK~n", []),
|
|
try
|
|
[_|_] = L = crypto:info_lib(),
|
|
io:format("CRYPTO_OK ~0p~n", [L])
|
|
catch
|
|
_ : _ ->
|
|
%% so logger has the chance to log something
|
|
timer:sleep(100),
|
|
halt(1)
|
|
|
|
end,
|
|
try
|
|
mnesia_hook:module_info(),
|
|
io:format("MNESIA_OK~n", [])
|
|
catch
|
|
_ : _ ->
|
|
io:format("WARNING: Mnesia app has no post-coommit hook support~n", []),
|
|
halt(2)
|
|
end,
|
|
halt(0).
|
|
'
|
|
[ "$DEBUG" -eq 1 ] && set -x
|
|
|
|
compatiblity_info() {
|
|
# RELEASE_LIB is used by Elixir
|
|
# set crash-dump bytes to zero to ensure no crash dump is generated when erl crashes
|
|
env ERL_CRASH_DUMP_BYTES=0 "$BINDIR/$PROGNAME" \
|
|
-noshell \
|
|
-boot "$REL_DIR/start_clean" \
|
|
-boot_var RELEASE_LIB "$ERTS_LIB_DIR/lib" \
|
|
-eval "$COMPATIBILITY_CHECK"
|
|
}
|
|
|
|
# Collect Erlang/OTP runtime sanity and compatibility in one go
|
|
maybe_use_portable_dynlibs() {
|
|
# Read BUILD_INFO early as the next commands may mess up the shell
|
|
BUILD_INFO="$(cat "${REL_DIR}/BUILD_INFO")"
|
|
COMPATIBILITY_INFO="$(compatiblity_info 2>/dev/null || true)"
|
|
if ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'CRYPTO_OK'); then
|
|
## failed to start, might be due to missing libs, try to be portable
|
|
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-$DYNLIBS_DIR}"
|
|
if [ "$LD_LIBRARY_PATH" != "$DYNLIBS_DIR" ]; then
|
|
export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
|
|
fi
|
|
## Turn off debug, because COMPATIBILITY_INFO needs to capture stderr
|
|
COMPATIBILITY_INFO="$(compatiblity_info 2>&1 || true)"
|
|
if ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'BEAM_OK'); then
|
|
## not able to start beam.smp
|
|
logerr "$COMPATIBILITY_INFO"
|
|
logerr "Please ensure it is running on the correct platform:"
|
|
logerr "$BUILD_INFO"
|
|
logerr "Version=$REL_VSN"
|
|
logerr "Required dependencies: openssl-1.1.1 (libcrypto), libncurses and libatomic1"
|
|
exit 1
|
|
elif ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'CRYPTO_OK'); then
|
|
## not able to start crypto app
|
|
logerr "$COMPATIBILITY_INFO"
|
|
exit 2
|
|
fi
|
|
logwarn "Using libs from '${DYNLIBS_DIR}' due to missing from the OS."
|
|
fi
|
|
}
|
|
|
|
SED_REPLACE="sed -i "
|
|
case $(sed --help 2>&1) in
|
|
*GNU*) SED_REPLACE="sed -i ";;
|
|
*BusyBox*) SED_REPLACE="sed -i ";;
|
|
*) SED_REPLACE="sed -i '' ";;
|
|
esac
|
|
|
|
# Get node pid
|
|
relx_get_pid() {
|
|
if output="$(relx_nodetool rpcterms os getpid)"
|
|
then
|
|
# shellcheck disable=SC2001 # Escaped quote taken as closing quote in editor
|
|
echo "$output" | sed -e 's/"//g'
|
|
return 0
|
|
else
|
|
echo "$output"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Connect to a remote node
|
|
remsh() {
|
|
# Generate a unique id used to allow multiple remsh to the same node
|
|
# transparently
|
|
id="remsh$(gen_node_id)-${NAME}"
|
|
|
|
# shellcheck disable=SC2086
|
|
# Setup remote shell command to control node
|
|
if [ "$IS_ELIXIR" = no ] || [ "${EMQX_CONSOLE_FLAVOR:-}" = 'erl' ] ; then
|
|
set -- "$BINDIR/erl" "$NAME_TYPE" "$id" \
|
|
-remsh "$NAME" -boot "$REL_DIR/start_clean" \
|
|
-boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
|
|
-boot_var RELEASE_LIB "$ERTS_LIB_DIR" \
|
|
-setcookie "$COOKIE" \
|
|
-hidden \
|
|
-kernel net_ticktime "$TICKTIME" \
|
|
$EPMD_ARGS
|
|
else
|
|
set -- "$REL_DIR/iex" \
|
|
--remsh "$NAME" \
|
|
--boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
|
|
--cookie "$COOKIE" \
|
|
--hidden \
|
|
--erl "-kernel net_ticktime $TICKTIME" \
|
|
--erl "$EPMD_ARGS" \
|
|
--erl "$NAME_TYPE $id" \
|
|
--boot "$REL_DIR/start_clean"
|
|
fi
|
|
exec "$@"
|
|
}
|
|
|
|
# Generate a random id
|
|
gen_node_id() {
|
|
od -t u -N 4 /dev/urandom | head -n1 | awk '{print $2 % 1000}'
|
|
}
|
|
|
|
call_nodetool() {
|
|
"$ERTS_DIR/bin/escript" "$RUNNER_ROOT_DIR/bin/nodetool" "$@"
|
|
}
|
|
|
|
# Control a node
|
|
relx_nodetool() {
|
|
command="$1"; shift
|
|
ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS -setcookie $COOKIE" \
|
|
call_nodetool "$NAME_TYPE" "$NAME" "$command" "$@"
|
|
}
|
|
|
|
call_hocon() {
|
|
call_nodetool hocon "$@" \
|
|
|| die "call_hocon_failed: $*" $?
|
|
}
|
|
|
|
find_emqx_process() {
|
|
## Find the running node from 'ps -ef'
|
|
## * The grep args like '[e]mqx' but not 'emqx' is to avoid greping the grep command itself
|
|
## * The running 'remsh' and 'nodetool' processes must be excluded
|
|
if [ -n "${EMQX_NODE__NAME:-}" ]; then
|
|
# if node name is provided, filter by node name
|
|
# shellcheck disable=SC2009
|
|
ps -ef | $GREP '[e]mqx' | $GREP -v -E '(remsh|nodetool)' | $GREP -E "\s-s?name\s${EMQX_NODE__NAME}" | $GREP -oE "\-[r]oot ${RUNNER_ROOT_DIR}.*" || true
|
|
else
|
|
# shellcheck disable=SC2009
|
|
ps -ef | $GREP '[e]mqx' | $GREP -v -E '(remsh|nodetool)' | $GREP -oE "\-[r]oot ${RUNNER_ROOT_DIR}.*" || true
|
|
fi
|
|
}
|
|
|
|
## Resolve boot configs in a batch
|
|
## This is because starting the Erlang beam with all modules loaded
|
|
## and parsing HOCON config + environment variables is a non-trivial task
|
|
CONF_KEYS=( 'node.data_dir' 'node.name' 'node.cookie' 'node.db_backend' 'cluster.proto_dist' 'node.dist_net_ticktime' )
|
|
if [ "$IS_ENTERPRISE" = 'yes' ]; then
|
|
CONF_KEYS+=( 'license.key' )
|
|
fi
|
|
|
|
## To be backward compatible, read and then unset EMQX_NODE_NAME
|
|
if [ -n "${EMQX_NODE_NAME:-}" ]; then
|
|
export EMQX_NODE__NAME="${EMQX_NODE_NAME}"
|
|
unset EMQX_NODE_NAME
|
|
fi
|
|
|
|
# Turn off debug as the ps output can be quite noisy
|
|
set +x
|
|
|
|
PS_LINE="$(find_emqx_process)"
|
|
logdebug "PS_LINE=$PS_LINE"
|
|
RUNNING_NODES_COUNT="$(echo -e "$PS_LINE" | sed '/^\s*$/d' | wc -l)"
|
|
[ "$RUNNING_NODES_COUNT" -gt 1 ] && logdebug "More than one running node found: count=$RUNNING_NODES_COUNT"
|
|
|
|
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
|
|
if [ "$RUNNING_NODES_COUNT" -gt 0 ] && [ "$COMMAND" != 'check_config' ]; then
|
|
running_node_name=$(echo -e "$PS_LINE" | $GREP -oE "\s-s?name.*" | awk '{print $2}' || true)
|
|
if [ -n "$running_node_name" ] && [ "$running_node_name" = "${EMQX_NODE__NAME:-}" ]; then
|
|
echo "Node ${running_node_name} is already running!"
|
|
exit 1
|
|
fi
|
|
fi
|
|
[ -f "$EMQX_ETC_DIR"/emqx.conf ] || die "emqx.conf is not found in $EMQX_ETC_DIR" 1
|
|
maybe_use_portable_dynlibs
|
|
if [ "${EMQX_BOOT_CONFIGS:-}" = '' ]; then
|
|
EMQX_BOOT_CONFIGS="$(call_hocon -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf multi_get "${CONF_KEYS[@]}")"
|
|
## export here so the 'console' command recursively called from
|
|
## 'start' command does not have to parse the configs again
|
|
export EMQX_BOOT_CONFIGS
|
|
fi
|
|
else
|
|
# For non-boot commands, we need below runtime facts to connect to the running node:
|
|
# 1. The running node name;
|
|
# 2. The Erlang cookie in use by the running node name;
|
|
# 3. SSL options if the node is using TLS for Erlang distribution;
|
|
# 4. Erlang kernel application's net_ticktime config.
|
|
#
|
|
# There are 3 sources of truth to get those runtime information.
|
|
# Listed in the order of preference:
|
|
# 1. The boot command (which can be inspected from 'ps -ef' command output)
|
|
# 2. The generated vm.<time>.config file located in the dir pointed by 'node.data_dir'
|
|
# 3. The bootstrap config 'etc/emqx.conf'
|
|
#
|
|
# If failed to read from source 1, the information is retrieved from source 3
|
|
# i.e. source 2 is never used.
|
|
#
|
|
# NOTES:
|
|
# * We should avoid getting runtime information with the 3rd approach because 'etc/emqx.conf' might
|
|
# be updated after the node is started. e.g. If a user starts the node with name 'emqx@127.0.0.1'
|
|
# then update the config in the file to 'node.name = "emqx@local.net"', after this change,
|
|
# there would be no way stop the running node 'emqx@127.0.0.1', because 'emqx stop' command
|
|
# would try to stop the new node instead.
|
|
if [ "$RUNNING_NODES_COUNT" -eq 1 ]; then
|
|
## only one emqx node is running, get running args from 'ps -ef' output
|
|
tmp_nodename=$(echo -e "$PS_LINE" | $GREP -oE "\s-s?name.*" | awk '{print $2}' || true)
|
|
tmp_cookie=$(echo -e "$PS_LINE" | $GREP -oE "\s-setcookie.*" | awk '{print $2}' || true)
|
|
tmp_proto_dist=$(echo -e "$PS_LINE" | $GREP -oE '\s-ekka_proto_dist.*' | awk '{print $2}' || echo 'inet_tcp')
|
|
SSL_DIST_OPTFILE="$(echo -e "$PS_LINE" | $GREP -oE '\-ssl_dist_optfile\s.+\s' | awk '{print $2}' || true)"
|
|
tmp_ticktime="$(echo -e "$PS_LINE" | $GREP -oE '\s-kernel\snet_ticktime\s.+\s' | awk '{print $3}' || true)"
|
|
# data_dir is actually not needed, but kept anyway
|
|
tmp_datadir="$(echo -e "$PS_LINE" | $GREP -oE "\-emqx_data_dir.*" | sed -E 's#.+emqx_data_dir[[:blank:]]##g' | sed -E 's#[[:blank:]]--$##g' || true)"
|
|
## Make the format like what call_hocon multi_get prints out, but only need 4 args
|
|
EMQX_BOOT_CONFIGS="node.name=${tmp_nodename}\nnode.cookie=${tmp_cookie}\ncluster.proto_dist=${tmp_proto_dist}\nnode.dist_net_ticktime=$tmp_ticktime\nnode.data_dir=${tmp_datadir}"
|
|
else
|
|
if [ "$RUNNING_NODES_COUNT" -gt 1 ]; then
|
|
if [ -z "${EMQX_NODE__NAME:-}" ]; then
|
|
tmp_nodenames=$(echo -e "$PS_LINE" | $GREP -oE "\s-s?name.*" | awk '{print $2}' | tr '\n' ' ')
|
|
logerr "More than one EMQX node found running (root dir: ${RUNNER_ROOT_DIR})"
|
|
logerr "Running nodes: $tmp_nodenames"
|
|
logerr "Make sure environment variable EMQX_NODE__NAME is set to indicate for which node this command is intended."
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ -n "${EMQX_NODE__NAME:-}" ]; then
|
|
die "Node $EMQX_NODE__NAME is not running?"
|
|
fi
|
|
fi
|
|
## We have no choice but to read the bootstrap config (with environment overrides available in the current shell)
|
|
[ -f "$EMQX_ETC_DIR"/emqx.conf ] || die "emqx.conf is not found in $EMQX_ETC_DIR" 1
|
|
maybe_use_portable_dynlibs
|
|
EMQX_BOOT_CONFIGS="$(call_hocon -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf multi_get "${CONF_KEYS[@]}")"
|
|
fi
|
|
fi
|
|
logdebug "EMQX_BOOT_CONFIGS: $EMQX_BOOT_CONFIGS"
|
|
[ "$DEBUG" -eq 1 ] && set -x
|
|
|
|
get_boot_config() {
|
|
path_to_value="$1"
|
|
echo -e "$EMQX_BOOT_CONFIGS" | $GREP "$path_to_value=" | sed -e "s/$path_to_value=//g" | tr -d \"
|
|
}
|
|
|
|
EPMD_ARGS="-start_epmd false -epmd_module ekka_epmd -proto_dist ekka"
|
|
PROTO_DIST="$(get_boot_config 'cluster.proto_dist' || true)"
|
|
TICKTIME="$(get_boot_config 'node.dist_net_ticktime' || echo '120')"
|
|
# this environment variable is required by ekka_dist module
|
|
# because proto_dist is overriden to ekka, and there is a lack of ekka_tls module
|
|
export EKKA_PROTO_DIST_MOD="${PROTO_DIST:-inet_tcp}"
|
|
if [ "$EKKA_PROTO_DIST_MOD" = 'inet_tls' ] || [ "$EKKA_PROTO_DIST_MOD" = 'inet6_tls' ]; then
|
|
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
|
|
SSL_DIST_OPTFILE=${EMQX_SSL_DIST_OPTFILE:-"$EMQX_ETC_DIR/ssl_dist.conf"}
|
|
case "$SSL_DIST_OPTFILE" in
|
|
*\ *)
|
|
# there is unfortunately no way to support space for this option because we'd need to grep
|
|
# from 'ps -ef' result to get this option for non-boot commands (nodetool) to run
|
|
set +x
|
|
logerr "Got space in: $SSL_DIST_OPTFILE"
|
|
logerr "No space is allowed for Erlang distribution over SSL option file path."
|
|
logerr "Configure it from environment variable EMQX_SSL_DIST_OPTFILE."
|
|
logerr "Or make sure emqx root path '$RUNNER_ROOT_DIR' has no space"
|
|
exit 1
|
|
;;
|
|
*)
|
|
true
|
|
;;
|
|
esac
|
|
fi
|
|
EPMD_ARGS="${EPMD_ARGS} -ssl_dist_optfile $SSL_DIST_OPTFILE"
|
|
fi
|
|
|
|
DATA_DIR="$(get_boot_config 'node.data_dir')"
|
|
# ensure no trailing /
|
|
DATA_DIR="${DATA_DIR%/}"
|
|
if [[ $DATA_DIR != /* ]]; then
|
|
# relative path
|
|
DATA_DIR="${RUNNER_ROOT_DIR}/${DATA_DIR}"
|
|
fi
|
|
CONFIGS_DIR="$DATA_DIR/configs"
|
|
mkdir -p "$CONFIGS_DIR"
|
|
|
|
check_license() {
|
|
if [ "$IS_ENTERPRISE" == "no" ]; then
|
|
return 0
|
|
fi
|
|
|
|
key_license="${EMQX_LICENSE__KEY:-$(get_boot_config 'license.key')}"
|
|
|
|
if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then
|
|
call_nodetool check_license_key "$key_license"
|
|
else
|
|
set +x
|
|
logerr "License not found."
|
|
logerr "Please specify one via the EMQX_LICENSE__KEY variable"
|
|
logerr "or via license.key in emqx.conf."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# When deciding which install upgrade script to run, we have to check
|
|
# our own version so we may avoid infinite loops and call the correct
|
|
# version.
|
|
current_script_version() {
|
|
curr_script=$(basename "${BASH_SOURCE[0]}")
|
|
suffix=${curr_script#*-}
|
|
if [[ "${suffix}" == "${curr_script}" ]]; then
|
|
# there's no suffix, so we're running the default `emqx` script;
|
|
# we'll have to trust the REL_VSN variable
|
|
echo "$REL_VSN"
|
|
else
|
|
echo "${suffix}"
|
|
fi
|
|
}
|
|
|
|
parse_semver() {
|
|
echo "$1" | tr '.|-' ' '
|
|
}
|
|
|
|
max_version_of() {
|
|
local vsn1="$1"
|
|
local vsn2="$2"
|
|
|
|
echo "${vsn1}" "${vsn2}" | tr " " "\n" | sort -rV | head -n1
|
|
}
|
|
|
|
versioned_script_path() {
|
|
local script_name="$1"
|
|
local vsn="$2"
|
|
|
|
echo "$RUNNER_ROOT_DIR/bin/$script_name-$vsn"
|
|
}
|
|
|
|
does_script_version_exist() {
|
|
local script_name="$1"
|
|
local vsn="$2"
|
|
|
|
if [[ -f "$(versioned_script_path "$script_name" "$vsn")" ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# extract_from_package packege_path destination file1 file2
|
|
extract_from_package() {
|
|
local package="$1"
|
|
local dest_dir="$2"
|
|
shift 2
|
|
|
|
tar -C "$dest_dir" -xf "$package" "$@"
|
|
}
|
|
|
|
am_i_the_newest_script() {
|
|
local curr_vsn other_vsn
|
|
curr_vsn="$(current_script_version)"
|
|
other_vsn="$1"
|
|
max_vsn="$(max_version_of "$other_vsn" "$curr_vsn")"
|
|
|
|
if [[ "$max_vsn" == "$curr_vsn" ]]; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
locate_package() {
|
|
local package_path candidates vsn
|
|
vsn="$1"
|
|
|
|
if [[ "${IS_ENTERPRISE}" == "yes" ]]; then
|
|
package_pattern="$RUNNER_ROOT_DIR/releases/emqx-enterprise-$vsn-*.tar.gz"
|
|
else
|
|
package_pattern="$RUNNER_ROOT_DIR/releases/emqx-$vsn-*.tar.gz"
|
|
fi
|
|
|
|
# shellcheck disable=SC2207,SC2086
|
|
candidates=($(ls $package_pattern))
|
|
|
|
if [[ "${#candidates[@]}" == 0 ]]; then
|
|
logerr "No package matching $package_pattern found."
|
|
exit 1
|
|
elif [[ "${#candidates[@]}" -gt 1 ]]; then
|
|
logerr "Multiple packages matching $package_pattern found. Ensure only one exists."
|
|
exit 1
|
|
else
|
|
echo "${candidates[0]}"
|
|
fi
|
|
}
|
|
|
|
ensure_newest_script_is_extracted() {
|
|
local newest_vsn="$1"
|
|
local package_path tmpdir
|
|
|
|
if does_script_version_exist "emqx" "$newest_vsn" \
|
|
&& does_script_version_exist "install_upgrade.escript" "$newest_vsn"; then
|
|
return
|
|
else
|
|
package_path="$(locate_package "$newest_vsn")"
|
|
tmpdir="$(mktemp -dp /tmp emqx.XXXXXXXXXXX)"
|
|
|
|
extract_from_package \
|
|
"$package_path" \
|
|
"$tmpdir" \
|
|
"bin/emqx-$newest_vsn" \
|
|
"bin/install_upgrade.escript-$newest_vsn"
|
|
|
|
cp "$tmpdir/bin/emqx-$newest_vsn" \
|
|
"$tmpdir/bin/install_upgrade.escript-$newest_vsn" \
|
|
"$RUNNER_ROOT_DIR/bin/"
|
|
|
|
rm -rf "$tmpdir"
|
|
fi
|
|
}
|
|
|
|
# Run an escript in the node's environment
|
|
relx_escript() {
|
|
shift; scriptpath="$1"; shift
|
|
"$ERTS_DIR/bin/escript" "$RUNNER_ROOT_DIR/$scriptpath" "$@"
|
|
}
|
|
|
|
# Output a start command for the last argument of run_erl
|
|
relx_start_command() {
|
|
printf "exec \"%s\" \"%s\"" "$RUNNER_SCRIPT" \
|
|
"$START_OPTION"
|
|
}
|
|
|
|
# Function to check configs without generating them
|
|
check_config() {
|
|
## this command checks the configs without generating any files
|
|
call_hocon -v -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf check_schema
|
|
}
|
|
|
|
# Function to generate app.config and vm.args
|
|
# sets two environment variables CONF_FILE and ARGS_FILE
|
|
generate_config() {
|
|
local name_type="$1"
|
|
local node_name="$2"
|
|
## Delete the *.siz files first or it can't start after
|
|
## changing the config 'log.rotation.size'
|
|
rm -f "${EMQX_LOG_DIR}"/*.siz
|
|
|
|
## timestamp for each generation
|
|
local NOW_TIME
|
|
NOW_TIME="$(date +'%Y.%m.%d.%H.%M.%S')"
|
|
|
|
## this command populates two files: app.<time>.config and vm.<time>.args
|
|
## NOTE: the generate command merges environment variables to the base config (emqx.conf),
|
|
## but does not include the cluster-override.conf and local-override.conf
|
|
## meaning, certain overrides will not be mapped to app.<time>.config file
|
|
call_hocon -v -t "$NOW_TIME" -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf -d "$DATA_DIR"/configs generate
|
|
|
|
## filenames are per-hocon convention
|
|
CONF_FILE="$CONFIGS_DIR/app.$NOW_TIME.config"
|
|
ARGS_FILE="$CONFIGS_DIR/vm.$NOW_TIME.args"
|
|
|
|
## Merge hocon generated *.args into the vm.args
|
|
TMP_ARG_FILE="$CONFIGS_DIR/vm.args.tmp"
|
|
cp "$EMQX_ETC_DIR/vm.args" "$TMP_ARG_FILE"
|
|
echo "" >> "$TMP_ARG_FILE"
|
|
echo "-pa \"${REL_DIR}/consolidated\"" >> "$TMP_ARG_FILE"
|
|
## read lines from generated vm.<time>.args file
|
|
## drop comment lines, and empty lines using sed
|
|
## pipe the lines to a while loop
|
|
sed '/^#/d' "$ARGS_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
|
|
## in the loop, split the 'key[:space:]value' pair
|
|
ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
|
|
ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
|
|
## use the key to look up in vm.args file for the value
|
|
TMP_ARG_VALUE=$($GREP "^$ARG_KEY" "$TMP_ARG_FILE" || true | awk '{print $NF}')
|
|
## compare generated (to override) value to original (to be overridden) value
|
|
if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
|
|
## if they are different
|
|
if [ -n "$TMP_ARG_VALUE" ]; then
|
|
## if the old value is present, replace it with generated value
|
|
sh -c "$SED_REPLACE 's|^$ARG_KEY.*$|$ARG_LINE|' \"$TMP_ARG_FILE\""
|
|
else
|
|
## otherwise append generated value to the end
|
|
echo "$ARG_LINE" >> "$TMP_ARG_FILE"
|
|
fi
|
|
fi
|
|
done
|
|
echo "$name_type $node_name" >> "$TMP_ARG_FILE"
|
|
echo "-mnesia dir '\"$DATA_DIR/mnesia/$NAME\"'" >> "$TMP_ARG_FILE"
|
|
## rename the generated vm.<time>.args file
|
|
mv -f "$TMP_ARG_FILE" "$ARGS_FILE"
|
|
}
|
|
|
|
# check if a PID is defunct
|
|
is_defunct() {
|
|
local PID="$1"
|
|
ps -fp "$PID" | $GREP -q 'defunct'
|
|
}
|
|
|
|
# check if a PID is down
|
|
# shellcheck disable=SC2317 # call in func `nodetool_shutdown()`
|
|
is_down() {
|
|
PID="$1"
|
|
if ps -p "$PID" >/dev/null; then
|
|
# still around
|
|
# shellcheck disable=SC2009 # this grep pattern is not a part of the program names
|
|
if is_defunct "$PID"; then
|
|
# zombie state, print parent pid
|
|
parent="$(ps -o ppid= -p "$PID" | tr -d ' ')"
|
|
if [ -z "$parent" ] && ! is_defunct "$PID"; then
|
|
# process terminated in the meanwhile
|
|
return 0;
|
|
fi
|
|
logwarn "$PID is marked <defunct>, parent: $(ps -p "$parent")"
|
|
return 0
|
|
fi
|
|
return 1
|
|
fi
|
|
# it's gone
|
|
return 0
|
|
}
|
|
|
|
wait_for() {
|
|
local WAIT_TIME
|
|
local CMD
|
|
WAIT_TIME="$1"
|
|
shift
|
|
CMD="$*"
|
|
while true; do
|
|
if $CMD; then
|
|
return 0
|
|
fi
|
|
if [ "$WAIT_TIME" -le 0 ]; then
|
|
return 1
|
|
fi
|
|
WAIT_TIME=$((WAIT_TIME - 1))
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
wait_until_return_val() {
|
|
local RESULT
|
|
local WAIT_TIME
|
|
local CMD
|
|
RESULT="$1"
|
|
WAIT_TIME="$2"
|
|
shift 2
|
|
CMD="$*"
|
|
while true; do
|
|
if [ "$($CMD 2>/dev/null)" = "$RESULT" ]; then
|
|
return 0
|
|
fi
|
|
if [ "$WAIT_TIME" -le 0 ]; then
|
|
return 1
|
|
fi
|
|
WAIT_TIME=$((WAIT_TIME - 1))
|
|
sleep 1
|
|
done
|
|
}
|
|
|
|
# First, there is EMQX_DEFAULT_LOG_HANDLER which can control the default values
|
|
# to be used when generating configs.
|
|
# It's set in docker entrypoint and in systemd service file.
|
|
#
|
|
# To be backward compatible with 4.x and v5.0.0 ~ v5.0.24/e5.0.2:
|
|
# if EMQX_LOG__TO is set, we try to enable handlers from environment variables.
|
|
# i.e. it overrides the default value set in EMQX_DEFAULT_LOG_HANDLER
|
|
tr_log_to_env() {
|
|
local log_to=${EMQX_LOG__TO:-undefined}
|
|
# unset because it's unknown to 5.0
|
|
unset EMQX_LOG__TO
|
|
case "${log_to}" in
|
|
console)
|
|
export EMQX_LOG__CONSOLE__ENABLE='true'
|
|
export EMQX_LOG__FILE__ENABLE='false'
|
|
;;
|
|
file)
|
|
export EMQX_LOG__CONSOLE__ENABLE='false'
|
|
export EMQX_LOG__FILE__ENABLE='true'
|
|
;;
|
|
both)
|
|
export EMQX_LOG__CONSOLE__ENABLE='true'
|
|
export EMQX_LOG__FILE__ENABLE='true'
|
|
;;
|
|
default)
|
|
# want to use config file defaults, do nothing
|
|
;;
|
|
undefined)
|
|
# value not set, do nothing
|
|
;;
|
|
*)
|
|
logerr "Unknown environment value for EMQX_LOG__TO=${log_to} discarded"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
maybe_log_to_console() {
|
|
if [ "${EMQX_LOG__TO:-}" = 'default' ]; then
|
|
# want to use defaults, do nothing
|
|
unset EMQX_LOG__TO
|
|
else
|
|
tr_log_to_env
|
|
export EMQX_DEFAULT_LOG_HANDLER=${EMQX_DEFAULT_LOG_HANDLER:-console}
|
|
fi
|
|
}
|
|
|
|
# Warn the user if ulimit -n is less than 1024
|
|
maybe_warn_ulimit() {
|
|
ULIMIT_F=$(ulimit -n)
|
|
if [ "$ULIMIT_F" -lt 1024 ]; then
|
|
logwarn "ulimit -n is ${ULIMIT_F}; 1024 is the recommended minimum."
|
|
fi
|
|
}
|
|
|
|
## Possible ways to configure emqx node name:
|
|
## 1. configure node.name in emqx.conf
|
|
## 2. override with environment variable EMQX_NODE__NAME
|
|
## Node name is either short-name (without '@'), e.g. 'emqx'
|
|
## or long name (with '@') e.g. 'emqx@example.net' or 'emqx@127.0.0.1'
|
|
NAME="${EMQX_NODE__NAME:-}"
|
|
if [ -z "$NAME" ]; then
|
|
NAME="$(get_boot_config 'node.name')"
|
|
fi
|
|
|
|
# force to use 'emqx' short name
|
|
[ -z "$NAME" ] && NAME='emqx'
|
|
|
|
case "$NAME" in
|
|
*@*)
|
|
NAME_TYPE='-name'
|
|
;;
|
|
*)
|
|
NAME_TYPE='-sname'
|
|
esac
|
|
SHORT_NAME="$(echo "$NAME" | awk -F'@' '{print $1}')"
|
|
HOST_NAME="$(echo "$NAME" | awk -F'@' '{print $2}')"
|
|
if ! (echo "$SHORT_NAME" | $GREP -q '^[0-9A-Za-z_\-]\+$'); then
|
|
logerr "Invalid node name, should be of format '^[0-9A-Za-z_-]+$'."
|
|
exit 1
|
|
fi
|
|
# This also changes the program name from 'beam.smp' to node name
|
|
# e.g. the 'ps' command output
|
|
export ESCRIPT_NAME="$SHORT_NAME"
|
|
|
|
PIPE_DIR="${PIPE_DIR:-/$DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}"
|
|
|
|
## Resolve Erlang cookie.
|
|
if [ -n "${EMQX_NODE_COOKIE:-}" ]; then
|
|
## To be backward compatible, read and unset EMQX_NODE_COOKIE
|
|
export EMQX_NODE__COOKIE="${EMQX_NODE_COOKIE}"
|
|
unset EMQX_NODE_COOKIE
|
|
fi
|
|
COOKIE="${EMQX_NODE__COOKIE:-}"
|
|
COOKIE_IN_USE="$(get_boot_config 'node.cookie')"
|
|
if [ "$IS_BOOT_COMMAND" != 'yes' ] && [ -n "$COOKIE_IN_USE" ] && [ -n "$COOKIE" ] && [ "$COOKIE" != "$COOKIE_IN_USE" ]; then
|
|
die "EMQX_NODE__COOKIE is different from the cookie used by $NAME"
|
|
fi
|
|
[ -z "$COOKIE" ] && COOKIE="$COOKIE_IN_USE"
|
|
[ -z "$COOKIE" ] && COOKIE="$EMQX_DEFAULT_ERLANG_COOKIE"
|
|
|
|
maybe_warn_default_cookie() {
|
|
if [ $IS_BOOT_COMMAND = 'yes' ] && [ "$COOKIE" = "$EMQX_DEFAULT_ERLANG_COOKIE" ]; then
|
|
logwarn "Default (insecure) Erlang cookie is in use."
|
|
logwarn "Configure node.cookie in $EMQX_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE__COOKIE"
|
|
logwarn "NOTE: Use the same cookie for all nodes in the cluster."
|
|
fi
|
|
}
|
|
|
|
## check if OTP version has mnesia_hook feature; if not, fallback to
|
|
## using Mnesia DB backend.
|
|
if [[ "$IS_BOOT_COMMAND" == 'yes' && "$(get_boot_config 'node.db_backend')" == "rlog" ]]; then
|
|
if ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'MNESIA_OK'); then
|
|
logwarn "DB Backend is RLOG, but an incompatible OTP version has been detected. Falling back to using Mnesia DB backend."
|
|
export EMQX_NODE__DB_BACKEND=mnesia
|
|
export EMQX_NODE__DB_ROLE=core
|
|
fi
|
|
fi
|
|
|
|
diagnose_boot_failure_and_die() {
|
|
local ps_line
|
|
local app_status
|
|
ps_line="$(find_emqx_process)"
|
|
if [ -z "$ps_line" ]; then
|
|
echo "Find more information in the latest log file: ${EMQX_LOG_DIR}/erlang.log.*"
|
|
exit 1
|
|
fi
|
|
if ! relx_nodetool "ping" > /dev/null; then
|
|
logerr "$NAME seems to be running, but not responding to pings."
|
|
echo "Make sure '$HOST_NAME' is a resolvable and reachable hostname."
|
|
pipe_shutdown
|
|
exit 2
|
|
fi
|
|
app_status="$(relx_nodetool 'eval' 'emqx:is_running()')"
|
|
if [ "$app_status" != 'true' ]; then
|
|
logerr "$NAME node is started, but failed to complete the boot sequence in time."
|
|
pipe_shutdown
|
|
exit 3
|
|
fi
|
|
}
|
|
|
|
## Only works when started in daemon mode
|
|
pipe_shutdown() {
|
|
if [ -d "$PIPE_DIR" ]; then
|
|
echo "Shutting down $NAME from to_erl pipe."
|
|
## can not evaluate init:stop() or erlang:halt() because the shell is restricted
|
|
echo 'emqx_machine:brutal_shutdown().' | "$BINDIR/to_erl" "$PIPE_DIR"
|
|
fi
|
|
}
|
|
|
|
## Call nodetool to stop EMQX
|
|
nodetool_shutdown() {
|
|
# Wait for the node to completely stop...
|
|
PID="$(relx_get_pid)"
|
|
if ! relx_nodetool "stop"; then
|
|
die "Graceful shutdown failed PID=[$PID]"
|
|
fi
|
|
WAIT_TIME="${EMQX_WAIT_FOR_STOP:-120}"
|
|
if ! wait_for "$WAIT_TIME" 'is_down' "$PID"; then
|
|
msg="dangling after ${WAIT_TIME} seconds"
|
|
# also log to syslog
|
|
logger -t "${REL_NAME}[${PID}]" "STOP: $msg"
|
|
# log to user console
|
|
set +x
|
|
logerr "Stop failed, $msg"
|
|
echo "ERROR: $PID is still around"
|
|
ps -p "$PID"
|
|
exit 1
|
|
fi
|
|
echo "ok"
|
|
logger -t "${REL_NAME}[${PID}]" "STOP: OK"
|
|
}
|
|
|
|
cd "$RUNNER_ROOT_DIR"
|
|
|
|
case "${COMMAND}" in
|
|
start)
|
|
maybe_warn_ulimit
|
|
maybe_warn_default_cookie
|
|
|
|
# this flag passes down to console mode
|
|
# so we know it's intended to be run in daemon mode
|
|
export _EMQX_START_DAEMON_MODE=1
|
|
|
|
case "$COMMAND" in
|
|
start)
|
|
shift
|
|
START_OPTION="console"
|
|
HEART_OPTION="start"
|
|
;;
|
|
esac
|
|
RUN_PARAM="$*"
|
|
|
|
# Set arguments for the heart command
|
|
set -- "$RUNNER_SCRIPT" "$HEART_OPTION"
|
|
[ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM"
|
|
|
|
# Export the HEART_COMMAND
|
|
HEART_COMMAND="$RUNNER_SCRIPT $COMMAND"
|
|
export HEART_COMMAND
|
|
|
|
## See: http://erlang.org/doc/man/run_erl.html
|
|
# Export the RUN_ERL_LOG_GENERATIONS
|
|
export RUN_ERL_LOG_GENERATIONS=${RUN_ERL_LOG_GENERATIONS:-"5"}
|
|
|
|
# Export the RUN_ERL_LOG_MAXSIZE
|
|
export RUN_ERL_LOG_MAXSIZE=${RUN_ERL_LOG_MAXSIZE:-"10485760"}
|
|
|
|
mkdir -p "$PIPE_DIR"
|
|
|
|
"$BINDIR/run_erl" -daemon "$PIPE_DIR" "$EMQX_LOG_DIR" \
|
|
"$(relx_start_command)"
|
|
|
|
WAIT_TIME=${EMQX_WAIT_FOR_START:-120}
|
|
if wait_until_return_val "true" "$WAIT_TIME" 'relx_nodetool' \
|
|
'eval' 'emqx:is_running()'; then
|
|
echo "$EMQX_DESCRIPTION $REL_VSN is started successfully!"
|
|
exit 0
|
|
else
|
|
logerr "${EMQX_DESCRIPTION} ${REL_VSN} using node name '${NAME}' failed ${WAIT_TIME} probes."
|
|
diagnose_boot_failure_and_die
|
|
fi
|
|
;;
|
|
|
|
stop)
|
|
if ! nodetool_shutdown; then
|
|
pipe_shutdown
|
|
fi
|
|
;;
|
|
|
|
pid)
|
|
## Get the VM's pid
|
|
if ! relx_get_pid; then
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
ping)
|
|
assert_node_alive
|
|
echo pong
|
|
;;
|
|
|
|
escript)
|
|
## Run an escript under the node's environment
|
|
if ! relx_escript "$@"; then
|
|
exit 1
|
|
fi
|
|
;;
|
|
|
|
attach)
|
|
exec "$BINDIR/to_erl" "$PIPE_DIR"
|
|
;;
|
|
|
|
remote_console)
|
|
assert_node_alive
|
|
|
|
shift
|
|
remsh
|
|
;;
|
|
|
|
upgrade|downgrade|install|unpack|uninstall)
|
|
if [ -z "${2:-}" ]; then
|
|
echo "Missing version argument"
|
|
echo "Usage: $REL_NAME $COMMAND {version}"
|
|
exit 1
|
|
fi
|
|
|
|
shift
|
|
|
|
assert_node_alive
|
|
|
|
curr_vsn="$(current_script_version)"
|
|
target_vsn="$1"
|
|
newest_vsn="$(max_version_of "$target_vsn" "$curr_vsn")"
|
|
ensure_newest_script_is_extracted "$newest_vsn"
|
|
# if we are not the newest script, run the same command from it
|
|
if ! am_i_the_newest_script "$newest_vsn"; then
|
|
script_path="$(versioned_script_path emqx "$newest_vsn")"
|
|
exec "$script_path" "$COMMAND" "$@"
|
|
fi
|
|
|
|
upgrade_script_path="$(versioned_script_path install_upgrade.escript "$newest_vsn")"
|
|
echo "using ${upgrade_script_path} to run ${COMMAND} $*"
|
|
|
|
ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
|
|
exec "$BINDIR/escript" "$upgrade_script_path" \
|
|
"$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
|
|
;;
|
|
|
|
versions)
|
|
assert_node_alive
|
|
|
|
shift
|
|
|
|
ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
|
|
exec "$BINDIR/escript" "$RUNNER_ROOT_DIR/bin/install_upgrade.escript" \
|
|
"versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
|
|
;;
|
|
|
|
console|console_clean|foreground)
|
|
# .boot file typically just $REL_NAME (ie, the app name)
|
|
# however, for debugging, sometimes start_clean.boot is useful.
|
|
# For e.g. 'setup', one may even want to name another boot script.
|
|
case "$COMMAND" in
|
|
console|foreground)
|
|
if [ -f "$REL_DIR/$REL_NAME.boot" ]; then
|
|
BOOTFILE="$REL_DIR/$REL_NAME"
|
|
else
|
|
BOOTFILE="$REL_DIR/start"
|
|
fi
|
|
;;
|
|
console_clean)
|
|
BOOTFILE="$REL_DIR/start_clean"
|
|
;;
|
|
esac
|
|
case "$COMMAND" in
|
|
foreground)
|
|
FOREGROUNDOPTIONS="-noshell -noinput +Bd"
|
|
;;
|
|
*)
|
|
FOREGROUNDOPTIONS=''
|
|
;;
|
|
esac
|
|
|
|
# set before generate_config
|
|
if [ "${_EMQX_START_DAEMON_MODE:-}" = 1 ]; then
|
|
tr_log_to_env
|
|
else
|
|
maybe_log_to_console
|
|
maybe_warn_ulimit
|
|
maybe_warn_default_cookie
|
|
fi
|
|
|
|
#generate app.config and vm.args
|
|
generate_config "$NAME_TYPE" "$NAME"
|
|
|
|
check_license
|
|
|
|
# Setup beam-required vars
|
|
EMU="beam"
|
|
PROGNAME="${0}"
|
|
|
|
export EMU
|
|
export PROGNAME
|
|
|
|
# Store passed arguments since they will be erased by `set`
|
|
ARGS="$*"
|
|
|
|
# shellcheck disable=SC2086
|
|
# Build an array of arguments to pass to exec later on
|
|
# Build it here because this command will be used for logging.
|
|
if [ "$IS_ELIXIR" = no ] || [ "${EMQX_CONSOLE_FLAVOR:-}" = 'erl' ] ; then
|
|
# pass down RELEASE_LIB so we can switch to IS_ELIXIR=no
|
|
# to boot an Erlang node from the elixir release
|
|
set -- "$BINDIR/erlexec" \
|
|
$FOREGROUNDOPTIONS \
|
|
-boot "$BOOTFILE" \
|
|
-boot_var RELEASE_LIB "$ERTS_LIB_DIR" \
|
|
-boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
|
|
-mode "$CODE_LOADING_MODE" \
|
|
-config "$CONF_FILE" \
|
|
-args_file "$ARGS_FILE" \
|
|
$EPMD_ARGS
|
|
else
|
|
set -- "$REL_DIR/iex" \
|
|
--boot "$BOOTFILE" \
|
|
--boot-var RELEASE_LIB "${ERTS_LIB_DIR}" \
|
|
--erl-config "${CONF_FILE}" \
|
|
--vm-args "${ARGS_FILE}" \
|
|
--erl "$FOREGROUNDOPTIONS" \
|
|
--erl "-mode $CODE_LOADING_MODE" \
|
|
--erl "$EPMD_ARGS" \
|
|
--werl
|
|
fi
|
|
|
|
# Log the startup
|
|
logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS} -ekka_proto_dist ${EKKA_PROTO_DIST_MOD} -emqx_data_dir ${DATA_DIR}"
|
|
|
|
# Start the VM
|
|
# add ekka_proto_dist emqx_data_dir to boot command so it is visible from 'ps -ef'
|
|
# NTOE: order matters! emqx_data_dir has to be positioned at the end of the line to simplify the
|
|
# line parsing when file path contains spaces
|
|
exec "$@" -- ${1+$ARGS} -ekka_proto_dist "${EKKA_PROTO_DIST_MOD}" -emqx_data_dir "${DATA_DIR}"
|
|
;;
|
|
|
|
ctl)
|
|
assert_node_alive
|
|
|
|
shift
|
|
|
|
relx_nodetool rpc_infinity emqx_ctl run_command "$@"
|
|
;;
|
|
rpc)
|
|
assert_node_alive
|
|
|
|
shift
|
|
|
|
relx_nodetool rpc "$@"
|
|
;;
|
|
rpcterms)
|
|
assert_node_alive
|
|
|
|
shift
|
|
|
|
relx_nodetool rpcterms "$@"
|
|
;;
|
|
eval)
|
|
assert_node_alive
|
|
|
|
shift
|
|
relx_nodetool "eval" "$@"
|
|
;;
|
|
eval-ex)
|
|
assert_node_alive
|
|
|
|
shift
|
|
if [ "$IS_ELIXIR" = "yes" ]
|
|
then
|
|
"$REL_DIR/elixir" \
|
|
--hidden \
|
|
--name "rand-$(gen_node_id)-$NAME" \
|
|
--cookie "$COOKIE" \
|
|
--boot "$REL_DIR/start_clean" \
|
|
--boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
|
|
--vm-args "$REL_DIR/remote.vm.args" \
|
|
--erl "-start_epmd false -epmd_module ekka_epmd" \
|
|
--rpc-eval "$NAME" "$@"
|
|
else
|
|
echo "EMQX node is not an Elixir node"
|
|
usage "$COMMAND"
|
|
exit 1
|
|
fi
|
|
;;
|
|
check_config)
|
|
check_config
|
|
;;
|
|
*)
|
|
usage "$COMMAND"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
exit 0
|