#!/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 ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)" # shellcheck disable=SC1090 . "$ROOT_DIR"/releases/emqx_vars # defined in emqx_vars export RUNNER_ROOT_DIR export RUNNER_ETC_DIR export REL_VSN RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME" CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}" REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN" SCHEMA_MOD=emqx_conf_schema WHOAMI=$(whoami) # Make sure log directory exists mkdir -p "$RUNNER_LOG_DIR" # Make sure data directory exists mkdir -p "$RUNNER_DATA_DIR" # Make sure data/configs exists CONFIGS_DIR="$RUNNER_DATA_DIR/configs" mkdir -p "$CONFIGS_DIR" # hocon try to read environment variables starting with "EMQX_" export HOCON_ENV_OVERRIDE_PREFIX='EMQX_' export ROOTDIR="$RUNNER_ROOT_DIR" export ERTS_DIR="$ROOTDIR/erts-$ERTS_VSN" export BINDIR="$ERTS_DIR/bin" export EMU="beam" export PROGNAME="erl" export ERTS_LIB_DIR="$ERTS_DIR/../lib" DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs" # Echo to stderr on errors echoerr() { echo "ERROR: $*" 1>&2; } die() { echoerr "ERROR: $1" errno=${2:-1} exit "$errno" } assert_node_alive() { if ! relx_nodetool "ping" > /dev/null; then die "node_is_not_running!" 1 fi } # Echo to stderr on errors echoerr() { echo "$*" 1>&2; } check_erlang_start() { # RELEASE_LIB is used by Elixir "$BINDIR/$PROGNAME" \ -noshell \ -boot_var RELEASE_LIB "$ERTS_LIB_DIR/lib" \ -boot "$REL_DIR/start_clean" \ -s crypto start \ -s erlang halt } usage() { local command="$1" 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) 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/-.tar.gz" echo " releases/-.zip" ;; 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/-.tar.gz" echo " releases/-.zip" 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/-.tar.gz" echo " releases/-.zip" 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/-.tar.gz" echo " releases/-.zip" echo "" echo " --no-permanent Install release package VERSION but" echo " don't make it permanent" ;; *) 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} " ;; 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="${LD_LIBRARY_PATH:-$DYNLIBS_DIR}" if [ "$LD_LIBRARY_PATH" != "$DYNLIBS_DIR" ]; then export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH" fi if ! check_erlang_start; then ## it's hopeless echoerr "FATAL: Unable to start Erlang." echoerr "Please make sure openssl-1.1.1 (libcrypto) and libncurses are installed." echoerr "Also ensure it's running on the correct platform," 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 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 priviledges 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 # EPMD_ARG="-start_epmd true $PROTO_DIST_ARG" NO_EPMD="-start_epmd false -epmd_module ekka_epmd -proto_dist ekka" EPMD_ARG="${EPMD_ARG:-${NO_EPMD}}" # Warn the user if ulimit -n is less than 1024 ULIMIT_F=$(ulimit -n) if [ "$ULIMIT_F" -lt 1024 ]; then echo "!!!!" echo "!!!! WARNING: ulimit -n is ${ULIMIT_F}; 1024 is the recommended minimum." echo "!!!!" 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 relx_rem_sh() { # Generate a unique id used to allow multiple remsh to the same node # transparently id="remsh$(relx_gen_id)-${NAME}" # Get the node's ticktime so that we use the same thing. TICKTIME="$(relx_nodetool rpcterms net_kernel get_net_ticktime)" # shellcheck disable=SC2086 # $EPMD_ARG is supposed to be split by whitespace # Setup remote shell command to control node if [ "$IS_ELIXIR" = "yes" ] then exec "$REL_DIR/iex" \ --remsh "$NAME" \ --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \ --cookie "$COOKIE" \ --hidden \ --erl "-kernel net_ticktime $TICKTIME" \ --erl "$EPMD_ARG" \ --erl "$NAME_TYPE $id" \ --boot "$REL_DIR/start_clean" else exec "$BINDIR/erl" "$NAME_TYPE" "$id" \ -remsh "$NAME" -boot "$REL_DIR/start_clean" \ -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ -setcookie "$COOKIE" -hidden -kernel net_ticktime "$TICKTIME" \ $EPMD_ARG fi } # Generate a random id relx_gen_id() { od -t x -N 4 /dev/urandom | head -n1 | awk '{print $2}' } # Control a node relx_nodetool() { command="$1"; shift ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARG" \ "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \ -setcookie "$COOKIE" "$command" "$@" } call_hocon() { "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" hocon "$@" \ || die "call_hocon_failed: $*" $? } # Run an escript in the node's environment relx_escript() { shift; scriptpath="$1"; shift "$ERTS_DIR/bin/escript" "$ROOTDIR/$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 generate app.config and vm.args generate_config() { local name_type="$1" local node_name="$2" ## Delete the *.siz files first or it cann't start after ## changing the config 'log.rotation.size' rm -rf "${RUNNER_LOG_DIR}"/*.siz EMQX_LICENSE_CONF_OPTION="" if [ "${EMQX_LICENSE_CONF:-}" != "" ]; then EMQX_LICENSE_CONF_OPTION="-c ${EMQX_LICENSE_CONF}" fi ## timestamp for each generation local NOW_TIME NOW_TIME="$(call_hocon now_time)" ## ths command populates two files: app.