Merge pull request #6117 from zmstone/chore-safer-bash-flags

refactor bin/emqx
This commit is contained in:
Zaiming (Stone) Shi 2021-11-12 07:48:09 +01:00 committed by GitHub
commit c89132e968
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 183 additions and 129 deletions

240
bin/emqx
View File

@ -2,8 +2,7 @@
# -*- tab-width:4;indent-tabs-mode:nil -*- # -*- tab-width:4;indent-tabs-mode:nil -*-
# ex: ts=4 sw=4 et # ex: ts=4 sw=4 et
set -e set -euo pipefail
set -o pipefail
DEBUG="${DEBUG:-0}" DEBUG="${DEBUG:-0}"
if [ "$DEBUG" -eq 1 ]; then if [ "$DEBUG" -eq 1 ]; then
@ -66,33 +65,80 @@ assert_node_alive() {
# Echo to stderr on errors # Echo to stderr on errors
echoerr() { echo "$*" 1>&2; } echoerr() { echo "$*" 1>&2; }
check_eralng_start() { check_erlang_start() {
"$BINDIR/$PROGNAME" -noshell -boot "$REL_DIR/start_clean" -s crypto start -s init stop "$BINDIR/$PROGNAME" -noshell -boot "$REL_DIR/start_clean" -s crypto start -s erlang halt
} }
if ! check_eralng_start >/dev/null 2>&1; then usage() {
BUILT_ON="$(head -1 "${REL_DIR}/BUILT_ON")" local command="$1"
## failed to start, might be due to missing libs, try to be portable
export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
if ! check_eralng_start; then
## it's hopeless
echoerr "FATAL: Unable to start Erlang (with libcrypto)."
echoerr "Please make sure it's running on the correct platform with all required dependencies."
echoerr "This EMQ X release is built for $BUILT_ON"
exit 1
fi
echoerr "WARNING: There seem to be missing dynamic libs from the OS. Using libs from ${DYNLIBS_DIR}"
fi
## backward compatible
if [ -d "$ERTS_DIR/lib" ]; then
export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
fi
relx_usage() {
command="$1"
case "$command" in case "$command" in
start)
echo "Start EMQ X service in daemon mode"
;;
stop)
echo "Stop the running EMQ X program"
;;
restart|reboot)
echo "Restart $EMQX_DESCRIPTION"
;;
pid)
echo "Print out $EMQX_DESCRIPTION process identifier"
;;
ping)
echo "Check if the $EMQX_DESCRIPTION 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 EMQ X package installation"
echo "For example $REL_NAME escript /path/to/my/escript my_arg1 my_arg2"
;;
attach)
echo "This command is applicable when $EMQX_DESCRIPTION is started in daemon"
echo "mode. it attaches the current shell to EMQ X's control console"
echo "through a named pipe"
echo "WARNING: try to use the safer alternative, remote_console command."
;;
remote_console)
echo "Start a dummy Erlang node and hidden-connect $EMQX_DESCRIPTION to"
echo "with an interactive Erlang shell"
;;
console)
echo "Boot up $EMQX_DESCRIPTION service in an interactive Erlang shell"
echo "This command is useful for troubleshooting"
;;
console_clean)
echo "This command does NOT boot up the $EMQX_DESCRIPTION service"
echo "It only starts an interactive Erlang console with all the"
echo "EMQ X code available"
;;
foreground)
echo "Start $EMQX_DESCRIPTION in foreground mode"
;;
ertspath)
echo "Print path to Erlang runtime dir"
;;
rpc)
echo "Usge $REL_NAME rpc MODULE FUNCTION [ARGS, ...]"
echo "Connect to the $EMQX_DESCRIPTION node and make an Erlang RPC"
echo "The result of the RPC call must be 'ok'"
echo "This command blocks for at most 60 seconds in case the node"
echo "does not reply the call in time"
;;
rpcterms)
echo "Usge $REL_NAME rpcterms MODULE FUNCTION [ARGS, ...]"
echo "Connect to the $EMQX_DESCRIPTION node and make an Erlang RPC"
echo "The result of the RPC call is pretty-printed as an Erlang term"
;;
root_dir)
echo "Print EMQ X installation root dir"
;;
eval)
echo "Evaluate an Erlang expression in the EMQ X node"
;;
versions)
echo "List installed EMQ X versions and their status"
;;
unpack) unpack)
echo "Usage: $REL_NAME unpack [VERSION]" echo "Usage: $REL_NAME unpack [VERSION]"
echo "Unpacks a release package VERSION, it assumes that this" echo "Unpacks a release package VERSION, it assumes that this"
@ -140,11 +186,40 @@ relx_usage() {
echo " don't make it permanent" echo " don't make it permanent"
;; ;;
*) *)
echo "Usage: $REL_NAME {start|start_boot <file>|ertspath|foreground|stop|pid|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|ctl|rpc|rpcterms|eval|root_dir}" echo "Usage: $REL_NAME {start|ertspath|foreground|stop|pid|ping|console|console_clean|attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|ctl|rpc|rpcterms|eval|root_dir} <help>"
;; ;;
esac esac
} }
COMMAND="${1:-}"
if [ "${2:-}" = 'help' ]; then
## 'ctl' command has its own usage info
if [ "$COMMAND" != 'ctl' ]; then
usage "$COMMAND"
exit 0
fi
fi
if ! check_erlang_start >/dev/null 2>&1; then
BUILT_ON="$(head -1 "${REL_DIR}/BUILT_ON")"
## failed to start, might be due to missing libs, try to be portable
export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
if ! check_erlang_start; then
## it's hopeless
echoerr "FATAL: Unable to start Erlang (with libcrypto)."
echoerr "Please make sure it's running on the correct platform with all required dependencies."
echoerr "This EMQ X release is built for $BUILT_ON"
exit 1
fi
echoerr "WARNING: There seem to be missing dynamic libs from the OS. Using libs from ${DYNLIBS_DIR}"
fi
## 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 # Simple way to check the correct user and fail early
check_user() { check_user() {
# Validate that the user running the script is the owner of the # Validate that the user running the script is the owner of the
@ -171,11 +246,9 @@ if [ "$ES" -ne 0 ]; then
exit $ES exit $ES
fi fi
if [ -z "$WITH_EPMD" ]; then # EPMD_ARG="-start_epmd true $PROTO_DIST_ARG"
EPMD_ARG="-start_epmd false -epmd_module ekka_epmd -proto_dist ekka" NO_EPMD="-start_epmd false -epmd_module ekka_epmd -proto_dist ekka"
else EPMD_ARG="${EPMD_ARG:-${NO_EPMD}}"
EPMD_ARG="-start_epmd true $PROTO_DIST_ARG"
fi
# Warn the user if ulimit -n is less than 1024 # Warn the user if ulimit -n is less than 1024
ULIMIT_F=$(ulimit -n) ULIMIT_F=$(ulimit -n)
@ -228,7 +301,7 @@ relx_gen_id() {
# Control a node # Control a node
relx_nodetool() { relx_nodetool() {
command="$1"; shift command="$1"; shift
ERL_FLAGS="$ERL_FLAGS $EPMD_ARG" \ ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \
"$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \ "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \
-setcookie "$COOKIE" "$command" "$@" -setcookie "$COOKIE" "$command" "$@"
} }
@ -358,25 +431,29 @@ wait_for() {
done done
} }
# Use $CWD/etc/sys.config if exists latest_vm_args() {
if [ -z "$RELX_CONFIG_PATH" ]; then local hint_var_name="$1"
if [ -f "$RUNNER_ETC_DIR/sys.config" ]; then local vm_args_file
RELX_CONFIG_PATH="-config $RUNNER_ETC_DIR/sys.config" vm_args_file="$(find "$CONFIGS_DIR" -type f -name "vm.*.args" | sort | tail -1)"
if [ -f "$vm_args_file" ]; then
echo "$vm_args_file"
else else
RELX_CONFIG_PATH="" echoerr "ERRRO: node not initialized?"
echoerr "Generated config file vm.*.args is not found for command '$COMMAND'"
echoerr "in config dir: $CONFIGS_DIR"
echoerr "In case the file has been deleted while the node is running,"
echoerr "set environment variable '$hint_var_name' to continue"
exit 1
fi fi
fi }
IS_BOOT_COMMAND='no' ## IS_BOOT_COMMAND is set for later to inspect node name and cookie from hocon config (or env variable)
case "$1" in case "${COMMAND}" in
start|start_boot) start|console|console_clean|foreground)
IS_BOOT_COMMAND='yes' IS_BOOT_COMMAND='yes'
;; ;;
console|console_clean|console_boot) *)
IS_BOOT_COMMAND='yes' IS_BOOT_COMMAND='no'
;;
foreground)
IS_BOOT_COMMAND='yes'
;; ;;
esac esac
@ -391,10 +468,8 @@ if [ -z "$NAME" ]; then
# for boot commands, inspect emqx.conf for node name # for boot commands, inspect emqx.conf for node name
NAME="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")" NAME="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.name | tr -d \")"
else else
# for non-boot commands, inspect vm.<time>.args for node name vm_args_file="$(latest_vm_args 'EMQX_NODE_NAME')"
# shellcheck disable=SC2012,SC2086 NAME="$(grep -E '^-s?name' "${vm_args_file}" | awk '{print $2}')"
LATEST_VM_ARGS="$(ls -t $CONFIGS_DIR/vm.*.args | head -1)"
NAME="$(grep -E '^-s?name' "$LATEST_VM_ARGS" | awk '{print $2}')"
fi fi
fi fi
@ -419,9 +494,8 @@ if [ -z "$COOKIE" ]; then
if [ "$IS_BOOT_COMMAND" = 'yes' ]; then if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
COOKIE="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie | tr -d \")" COOKIE="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get node.cookie | tr -d \")"
else else
# shellcheck disable=SC2012,SC2086 vm_args_file="$(latest_vm_args 'EMQX_NODE_COOKIE')"
LATEST_VM_ARGS="$(ls -t $CONFIGS_DIR/vm.*.args | head -1)" COOKIE="$(grep -E '^-setcookie' "${vm_args_file}" | awk '{print $2}')"
COOKIE="$(grep -E '^-setcookie' "$LATEST_VM_ARGS" | awk '{print $2}')"
fi fi
fi fi
@ -429,18 +503,10 @@ if [ -z "$COOKIE" ]; then
die "Please set node.cookie in $RUNNER_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE_COOKIE" die "Please set node.cookie in $RUNNER_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE_COOKIE"
fi fi
# Support for IPv6 Dist. See: https://github.com/emqtt/emqttd/issues/1460
PROTO_DIST="$(call_hocon -s $SCHEMA_MOD -I "$CONFIGS_DIR/" -c "$RUNNER_ETC_DIR"/emqx.conf get cluster.proto_dist | tr -d \")"
if [ -z "$PROTO_DIST" ]; then
PROTO_DIST_ARG=""
else
PROTO_DIST_ARG="-proto_dist $PROTO_DIST"
fi
cd "$ROOTDIR" cd "$ROOTDIR"
case "$1" in case "${COMMAND}" in
start|start_boot) start)
# Make sure a node IS not running # Make sure a node IS not running
if relx_nodetool "ping" >/dev/null 2>&1; then if relx_nodetool "ping" >/dev/null 2>&1; then
die "node_is_already_running!" die "node_is_already_running!"
@ -450,21 +516,14 @@ case "$1" in
# this flag passes down to console mode # this flag passes down to console mode
# so we know it's intended to be run in daemon mode # so we know it's intended to be run in daemon mode
export _EMQX_START_MODE="$1" export _EMQX_START_MODE="$COMMAND"
# Save this for later. case "$COMMAND" in
CMD=$1
case "$1" in
start) start)
shift shift
START_OPTION="console" START_OPTION="console"
HEART_OPTION="start" HEART_OPTION="start"
;; ;;
start_boot)
shift
START_OPTION="console_boot"
HEART_OPTION="start_boot"
;;
esac esac
RUN_PARAM="$*" RUN_PARAM="$*"
@ -473,7 +532,7 @@ case "$1" in
[ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM" [ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM"
# Export the HEART_COMMAND # Export the HEART_COMMAND
HEART_COMMAND="$RUNNER_SCRIPT $CMD" HEART_COMMAND="$RUNNER_SCRIPT $COMMAND"
export HEART_COMMAND export HEART_COMMAND
## See: http://erlang.org/doc/man/run_erl.html ## See: http://erlang.org/doc/man/run_erl.html
@ -523,8 +582,8 @@ case "$1" in
;; ;;
restart|reboot) restart|reboot)
echo "$EMQX_DESCRIPTION $REL_VSN is stopped: $("$RUNNER_BIN_DIR"/emqx stop)" echo "$EMQX_DESCRIPTION $REL_VSN is stopped: $("$RUNNER_BIN_DIR/$REL_NAME" stop)"
"$RUNNER_BIN_DIR"/emqx start "$RUNNER_BIN_DIR/$REL_NAME" start
;; ;;
pid) pid)
@ -566,17 +625,17 @@ case "$1" in
;; ;;
upgrade|downgrade|install|unpack|uninstall) upgrade|downgrade|install|unpack|uninstall)
if [ -z "$2" ]; then if [ -z "${2:-}" ]; then
echo "Missing version argument" echo "Missing version argument"
echo "Usage: $REL_NAME $1 {version}" echo "Usage: $REL_NAME $COMMAND {version}"
exit 1 exit 1
fi fi
COMMAND="$1"; shift shift
assert_node_alive assert_node_alive
ERL_FLAGS="$ERL_FLAGS $EPMD_ARG" \ ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \
exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \ exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
"$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@" "$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
;; ;;
@ -584,21 +643,21 @@ case "$1" in
versions) versions)
assert_node_alive assert_node_alive
COMMAND="$1"; shift shift
ERL_FLAGS="$ERL_FLAGS $EPMD_ARG" \ ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \
exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \ exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
"versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@" "versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
;; ;;
console|console_clean|console_boot) console|console_clean)
# Bootstrap daemon command (check perms & drop to $RUNNER_USER) # Bootstrap daemon command (check perms & drop to $RUNNER_USER)
bootstrapd bootstrapd
# .boot file typically just $REL_NAME (ie, the app name) # .boot file typically just $REL_NAME (ie, the app name)
# however, for debugging, sometimes start_clean.boot is useful. # however, for debugging, sometimes start_clean.boot is useful.
# For e.g. 'setup', one may even want to name another boot script. # For e.g. 'setup', one may even want to name another boot script.
case "$1" in case "$COMMAND" in
console) console)
if [ -f "$REL_DIR/$REL_NAME.boot" ]; then if [ -f "$REL_DIR/$REL_NAME.boot" ]; then
BOOTFILE="$REL_DIR/$REL_NAME" BOOTFILE="$REL_DIR/$REL_NAME"
@ -609,11 +668,6 @@ case "$1" in
console_clean) console_clean)
BOOTFILE="$REL_DIR/start_clean" BOOTFILE="$REL_DIR/start_clean"
;; ;;
console_boot)
shift
BOOTFILE="$1"
shift
;;
esac esac
# set before generate_config # set before generate_config
@ -634,14 +688,14 @@ case "$1" in
# Store passed arguments since they will be erased by `set` # Store passed arguments since they will be erased by `set`
ARGS="$*" ARGS="$*"
# shellcheck disable=SC2086 # $RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace # shellcheck disable=SC2086 # $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace
# Build an array of arguments to pass to exec later on # Build an array of arguments to pass to exec later on
# Build it here because this command will be used for logging. # Build it here because this command will be used for logging.
set -- "$BINDIR/erlexec" \ set -- "$BINDIR/erlexec" \
-boot "$BOOTFILE" -mode "$CODE_LOADING_MODE" \ -boot "$BOOTFILE" -mode "$CODE_LOADING_MODE" \
-boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
-mnesia dir "\"${MNESIA_DATA_DIR}\"" \ -mnesia dir "\"${MNESIA_DATA_DIR}\"" \
$RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG $CONFIG_ARGS $EPMD_ARG
# Log the startup # Log the startup
logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS}" logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS}"
@ -675,14 +729,14 @@ case "$1" in
# Store passed arguments since they will be erased by `set` # Store passed arguments since they will be erased by `set`
ARGS="$*" ARGS="$*"
# shellcheck disable=SC2086 # $RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace # shellcheck disable=SC2086 # $CONFIG_ARGS $EPMD_ARG are supposed to be split by whitespace
# Build an array of arguments to pass to exec later on # Build an array of arguments to pass to exec later on
# Build it here because this command will be used for logging. # Build it here because this command will be used for logging.
set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \ set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \
-boot "$REL_DIR/$BOOTFILE" -mode "$CODE_LOADING_MODE" \ -boot "$REL_DIR/$BOOTFILE" -mode "$CODE_LOADING_MODE" \
-boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
-mnesia dir "\"${MNESIA_DATA_DIR}\"" \ -mnesia dir "\"${MNESIA_DATA_DIR}\"" \
$RELX_CONFIG_PATH $CONFIG_ARGS $EPMD_ARG $CONFIG_ARGS $EPMD_ARG
# Log the startup # Log the startup
logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS}" logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS}"
@ -727,7 +781,7 @@ case "$1" in
relx_nodetool "eval" "$@" relx_nodetool "eval" "$@"
;; ;;
*) *)
relx_usage "$1" usage "$COMMAND"
exit 1 exit 1
;; ;;
esac esac