#!/bin/bash # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et set -e ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)" # shellcheck disable=SC1090 . "$ROOT_DIR"/releases/emqx_vars RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME" CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}" REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN" WHOAMI=$(whoami) # Make sure log directory exists mkdir -p "$RUNNER_LOG_DIR" # Make sure data directory exists mkdir -p "$RUNNER_DATA_DIR" export ROOTDIR="$RUNNER_ROOT_DIR" export ERTS_DIR="$ROOTDIR/erts-$ERTS_VSN" export BINDIR="$ERTS_DIR/bin" export EMU="beam" export PROGNAME="erl" DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs" ERTS_LIB_DIR="$ERTS_DIR/../lib" # Echo to stderr on errors echoerr() { echo "$*" 1>&2; } check_eralng_start() { "$BINDIR/$PROGNAME" -noshell -boot "$REL_DIR/start_clean" -s crypto start -s init stop } if ! check_eralng_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_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 # cuttlefish try to read environment variables starting with "EMQX_" export CUTTLEFISH_ENV_OVERRIDE_PREFIX='EMQX_' relx_usage() { command="$1" case "$command" in 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|start_boot |ertspath|foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot |attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|rpc|rpcterms|eval|root_dir}" ;; esac } # 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="\"$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 if [ -z "$WITH_EPMD" ]; then EPMD_ARG="-start_epmd false -epmd_module ekka_epmd -proto_dist ekka" else EPMD_ARG="-start_epmd true $PROTO_DIST_ARG" fi # 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 # By default, use cuttlefish to generate app.config and vm.args CUTTLEFISH="${USE_CUTTLEFISH:-yes}" 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 } relx_get_nodename() { id="longname$(relx_gen_id)-${NAME}" "$BINDIR/erl" -boot "$REL_DIR/start_clean" -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell "${NAME_TYPE}" "$id" } # 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 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 } # 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 export RUNNER_ROOT_DIR export REL_VSN ERL_FLAGS="$ERL_FLAGS $EPMD_ARG" \ "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \ -setcookie "$COOKIE" "$command" "$@" } # Run an escript in the node's environment relx_escript() { shift; scriptpath="$1"; shift export RUNNER_ROOT_DIR "$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() { ## Delete the *.siz files first or it cann't start after ## changing the config 'log.rotation.size' rm -rf "${RUNNER_LOG_DIR}"/*.siz if [ "$CUTTLEFISH" != "yes" ]; then # Note: we have added a parameter '-vm_args' to this. It # appears redundant but it is not! the erlang vm allows us to # access all arguments to the erl command EXCEPT '-args_file', # so in order to get access to this file location from within # the vm, we need to pass it in twice. CONFIG_ARGS=" -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -vm_args $RUNNER_ETC_DIR/vm.args " else EMQX_LICENSE_CONF_OPTION="" if [ "${EMQX_LICENSE_CONF:-}" != "" ]; then EMQX_LICENSE_CONF_OPTION="-i ${EMQX_LICENSE_CONF}" fi set +e # shellcheck disable=SC2086 CUTTLEFISH_OUTPUT="$("$ERTS_PATH"/escript "$RUNNER_ROOT_DIR"/bin/cuttlefish -v -i "$REL_DIR"/emqx.schema $EMQX_LICENSE_CONF_OPTION -c "$RUNNER_ETC_DIR"/emqx.conf -d "$RUNNER_DATA_DIR"/configs generate)" # shellcheck disable=SC2181 RESULT=$? set -e if [ $RESULT -gt 0 ]; then echo "$CUTTLEFISH_OUTPUT" exit $RESULT fi # print override from environment variables (EMQX_*) echo "$CUTTLEFISH_OUTPUT" | sed -e '$d' CONFIG_ARGS=$(echo "$CUTTLEFISH_OUTPUT" | tail -n 1) ## Merge cuttlefish generated *.args into the vm.args CUTTLE_GEN_ARG_FILE=$(echo "$CONFIG_ARGS" | sed -n 's/^.*\(vm_args[[:space:]]\)//p' | awk '{print $1}') TMP_ARG_FILE="$RUNNER_DATA_DIR/configs/vm.args.tmp" cp "$RUNNER_ETC_DIR/vm.args" "$TMP_ARG_FILE" echo "" >> "$TMP_ARG_FILE" echo "-pa ${REL_DIR}/consolidated" >> "$TMP_ARG_FILE" sed '/^#/d' "$CUTTLE_GEN_ARG_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}') ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}') if [ "$ARG_KEY" = '' ]; then ## for the flags, e.g. -heart -emu_args etc ARG_KEY=$(echo "$ARG_LINE" | awk '{print $1}') ARG_VALUE='' TMP_ARG_KEY=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $1}') if [ "$TMP_ARG_KEY" = '' ]; then echo "$ARG_KEY" >> "$TMP_ARG_FILE" fi else TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" | awk '{print $NF}') if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then if [ -n "$TMP_ARG_VALUE" ]; then sh -c "$SED_REPLACE 's/^$ARG_KEY.*$/$ARG_LINE/' $TMP_ARG_FILE" else echo "$ARG_LINE" >> "$TMP_ARG_FILE" fi fi fi done mv -f "$TMP_ARG_FILE" "$CUTTLE_GEN_ARG_FILE" fi # shellcheck disable=SC2086 if ! relx_nodetool chkconfig $CONFIG_ARGS; then echoerr "Error reading $CONFIG_ARGS" exit 1 fi } # Call bootstrapd for daemon commands like start/stop/console bootstrapd() { if [ -e "$RUNNER_DATA_DIR/.erlang.cookie" ]; then chown "$RUNNER_USER" "$RUNNER_DATA_DIR"/.erlang.cookie fi } # Use $CWD/etc/sys.config if exists if [ -z "$RELX_CONFIG_PATH" ]; then if [ -f "$RUNNER_ETC_DIR/sys.config" ]; then RELX_CONFIG_PATH="-config $RUNNER_ETC_DIR/sys.config" else RELX_CONFIG_PATH="" fi fi IS_BOOT_COMMAND='no' case "$1" in start|start_boot) IS_BOOT_COMMAND='yes' ;; console|console_clean|console_boot) IS_BOOT_COMMAND='yes' ;; foreground) IS_BOOT_COMMAND='yes' ;; esac if [ -z "$NAME_ARG" ]; then NODENAME="${EMQX_NODE_NAME:-}" # compatible with docker entrypoint [ -z "$NODENAME" ] && [ -n "$EMQX_NAME" ] && [ -n "$EMQX_HOST" ] && NODENAME="${EMQX_NAME}@${EMQX_HOST}" if [ -z "$NODENAME" ]; then if [ "$IS_BOOT_COMMAND" = 'no' ]; then # for non-boot commands, inspect vm.