emqx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. #!/usr/bin/env bash
  2. # -*- tab-width:4;indent-tabs-mode:nil -*-
  3. # ex: ts=4 sw=4 et
  4. set -euo pipefail
  5. DEBUG="${DEBUG:-0}"
  6. [ "$DEBUG" -eq 1 ] && set -x
  7. RUNNER_ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
  8. # shellcheck disable=SC1090,SC1091
  9. . "$RUNNER_ROOT_DIR"/releases/emqx_vars
  10. # defined in emqx_vars
  11. export RUNNER_ROOT_DIR
  12. export EMQX_ETC_DIR
  13. export REL_VSN
  14. export SCHEMA_MOD
  15. RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
  16. CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
  17. REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN"
  18. WHOAMI=$(whoami)
  19. # Make sure log directory exists
  20. mkdir -p "$RUNNER_LOG_DIR"
  21. # hocon try to read environment variables starting with "EMQX_"
  22. export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
  23. export ERTS_DIR="$RUNNER_ROOT_DIR/erts-$ERTS_VSN"
  24. export BINDIR="$ERTS_DIR/bin"
  25. export EMU="beam"
  26. export PROGNAME="erl"
  27. export ERTS_LIB_DIR="$RUNNER_ROOT_DIR/lib"
  28. DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs"
  29. # Echo to stderr on errors
  30. echoerr() {
  31. echo -e "$*" 1>&2;
  32. }
  33. die() {
  34. set +x
  35. echoerr "ERROR: $1"
  36. errno=${2:-1}
  37. exit "$errno"
  38. }
  39. assert_node_alive() {
  40. if ! relx_nodetool "ping" > /dev/null; then
  41. die "node_is_not_running!" 1
  42. fi
  43. }
  44. usage() {
  45. local command="$1"
  46. case "$command" in
  47. start)
  48. echo "Start EMQX service in daemon mode"
  49. ;;
  50. stop)
  51. echo "Stop the running EMQX program"
  52. ;;
  53. console)
  54. echo "Boot up EMQX service in an interactive Erlang or Elixir shell"
  55. echo "This command needs a tty"
  56. ;;
  57. console_clean)
  58. echo "This command does NOT boot up the EMQX service"
  59. echo "It only starts an interactive Erlang or Elixir console with all the"
  60. echo "EMQX code available"
  61. ;;
  62. foreground)
  63. echo "Start EMQX in foreground mode without an interactive shell"
  64. ;;
  65. pid)
  66. echo "Print out EMQX process identifier"
  67. ;;
  68. ping)
  69. echo "Check if the EMQX node is up and running"
  70. echo "This command exit with 0 silently if node is running"
  71. ;;
  72. escript)
  73. echo "Execute a escript using the Erlang runtime from EMQX package installation"
  74. echo "For example $REL_NAME escript /path/to/my/escript my_arg1 my_arg2"
  75. ;;
  76. attach)
  77. echo "This command is applicable when EMQX is started in daemon mode."
  78. echo "It attaches the current shell to EMQX's control console"
  79. echo "through a named pipe."
  80. echo "WARNING: try to use the safer alternative, remote_console command."
  81. ;;
  82. remote_console)
  83. echo "Start an interactive shell running an Erlang or Elixir node which "
  84. echo "hidden-connects to the running EMQX node".
  85. echo "This command is mostly used for troubleshooting."
  86. ;;
  87. ertspath)
  88. echo "Print path to Erlang runtime bin dir"
  89. ;;
  90. rpc)
  91. echo "Usge $REL_NAME rpc MODULE FUNCTION [ARGS, ...]"
  92. echo "Connect to the EMQX node and make an Erlang RPC"
  93. echo "This command blocks for at most 60 seconds."
  94. echo "It exits with non-zero code in case of any RPC failure"
  95. echo "including connection error and runtime exception"
  96. ;;
  97. rpcterms)
  98. echo "Usge $REL_NAME rpcterms MODULE FUNCTION [ARGS, ...]"
  99. echo "Connect to the EMQX node and make an Erlang RPC"
  100. echo "The result of the RPC call is pretty-printed as an "
  101. echo "Erlang term"
  102. ;;
  103. root_dir)
  104. echo "Print EMQX installation root dir"
  105. ;;
  106. eval)
  107. echo "Evaluate an Erlang or Elixir expression in the EMQX node"
  108. ;;
  109. eval-erl)
  110. echo "Evaluate an Erlang expression in the EMQX node, even on Elixir node"
  111. ;;
  112. versions)
  113. echo "List installed EMQX versions and their status"
  114. ;;
  115. unpack)
  116. echo "Usage: $REL_NAME unpack [VERSION]"
  117. echo "Unpacks a release package VERSION, it assumes that this"
  118. echo "release package tarball has already been deployed at one"
  119. echo "of the following locations:"
  120. echo " releases/<relname>-<version>.tar.gz"
  121. ;;
  122. install)
  123. echo "Usage: $REL_NAME install [VERSION]"
  124. echo "Installs a release package VERSION, it assumes that this"
  125. echo "release package tarball has already been deployed at one"
  126. echo "of the following locations:"
  127. echo " releases/<relname>-<version>.tar.gz"
  128. echo ""
  129. echo " --no-permanent Install release package VERSION but"
  130. echo " don't make it permanent"
  131. ;;
  132. uninstall)
  133. echo "Usage: $REL_NAME uninstall [VERSION]"
  134. echo "Uninstalls a release VERSION, it will only accept"
  135. echo "versions that are not currently in use"
  136. ;;
  137. upgrade)
  138. echo "Usage: $REL_NAME upgrade [VERSION]"
  139. echo "Upgrades the currently running release to VERSION, it assumes"
  140. echo "that a release package tarball has already been deployed at one"
  141. echo "of the following locations:"
  142. echo " releases/<relname>-<version>.tar.gz"
  143. echo ""
  144. echo " --no-permanent Install release package VERSION but"
  145. echo " don't make it permanent"
  146. ;;
  147. downgrade)
  148. echo "Usage: $REL_NAME downgrade [VERSION]"
  149. echo "Downgrades the currently running release to VERSION, it assumes"
  150. echo "that a release package tarball has already been deployed at one"
  151. echo "of the following locations:"
  152. echo " releases/<relname>-<version>.tar.gz"
  153. echo ""
  154. echo " --no-permanent Install release package VERSION but"
  155. echo " don't make it permanent"
  156. ;;
  157. *)
  158. echo "Usage: $REL_NAME COMMAND [help]"
  159. echo ''
  160. echo "Commonly used COMMANDs:"
  161. echo " start: Start EMQX in daemon mode"
  162. echo " console: Start EMQX in an interactive Erlang or Elixir shell"
  163. echo " foreground: Start EMQX in foreground mode without an interactive shell"
  164. echo " stop: Stop the running EMQX node"
  165. echo " ctl: Administration commands, execute '$REL_NAME ctl help' for more details"
  166. echo ''
  167. echo "More:"
  168. echo " Shell attach: remote_console | attach"
  169. echo " Up/Down-grade: upgrade | downgrade | install | uninstall"
  170. echo " Install info: ertspath | root_dir"
  171. echo " Runtime info: pid | ping | versions"
  172. echo " Advanced: console_clean | escript | rpc | rpcterms | eval | eval-erl"
  173. echo ''
  174. echo "Execute '$REL_NAME COMMAND help' for more information"
  175. ;;
  176. esac
  177. }
  178. COMMAND="${1:-}"
  179. if [ -z "$COMMAND" ]; then
  180. usage 'help'
  181. exit 1
  182. elif [ "$COMMAND" = 'help' ]; then
  183. usage 'help'
  184. exit 0
  185. fi
  186. if [ "${2:-}" = 'help' ]; then
  187. ## 'ctl' command has its own usage info
  188. if [ "$COMMAND" != 'ctl' ]; then
  189. usage "$COMMAND"
  190. exit 0
  191. fi
  192. fi
  193. ## IS_BOOT_COMMAND is set for later to inspect node name and cookie from hocon config (or env variable)
  194. case "${COMMAND}" in
  195. start|console|console_clean|foreground)
  196. IS_BOOT_COMMAND='yes'
  197. ;;
  198. ertspath)
  199. echo "$ERTS_DIR"
  200. exit 0
  201. ;;
  202. root_dir)
  203. echo "$RUNNER_ROOT_DIR"
  204. exit 0
  205. ;;
  206. *)
  207. IS_BOOT_COMMAND='no'
  208. ;;
  209. esac
  210. ## backward compatible
  211. if [ -d "$ERTS_DIR/lib" ]; then
  212. export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
  213. fi
  214. # Simple way to check the correct user and fail early
  215. check_user() {
  216. # Validate that the user running the script is the owner of the
  217. # RUN_DIR.
  218. if [ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]; then
  219. if [ "x$WHOAMI" != "xroot" ]; then
  220. echo "You need to be root or use sudo to run this command"
  221. exit 1
  222. fi
  223. CMD="DEBUG=$DEBUG \"$RUNNER_SCRIPT\" "
  224. for ARG in "$@"; do
  225. CMD="${CMD} \"$ARG\""
  226. done
  227. # This will drop privileges into the runner user
  228. # It exec's in a new shell and the current shell will exit
  229. exec su - "$RUNNER_USER" -c "$CMD"
  230. fi
  231. }
  232. # Make sure the user running this script is the owner and/or su to that user
  233. check_user "$@"
  234. ES=$?
  235. if [ "$ES" -ne 0 ]; then
  236. exit $ES
  237. fi
  238. COMPATIBILITY_CHECK='
  239. io:format("BEAM_OK~n", []),
  240. try
  241. [_|_] = L = crypto:info_lib(),
  242. io:format("CRYPTO_OK ~0p~n", [L])
  243. catch
  244. _ : _ ->
  245. %% so logger has the chance to log something
  246. timer:sleep(100),
  247. halt(1)
  248. end,
  249. try
  250. mnesia_hook:module_info(),
  251. io:format("MNESIA_OK~n", [])
  252. catch
  253. _ : _ ->
  254. io:format("WARNING: Mnesia app has no post-coommit hook support~n", []),
  255. halt(2)
  256. end,
  257. halt(0).
  258. '
  259. compatiblity_info() {
  260. # RELEASE_LIB is used by Elixir
  261. "$BINDIR/$PROGNAME" \
  262. -noshell \
  263. -boot_var RELEASE_LIB "$ERTS_LIB_DIR/lib" \
  264. -boot "$REL_DIR/start_clean" \
  265. -eval "$COMPATIBILITY_CHECK"
  266. }
  267. # Collect Erlang/OTP runtime sanity and compatibility in one go
  268. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  269. # Read BUILD_INFO early as the next commands may mess up the shell
  270. BUILD_INFO="$(cat "${REL_DIR}/BUILD_INFO")"
  271. COMPATIBILITY_INFO="$(compatiblity_info 2>/dev/null || true)"
  272. if ! (echo -e "$COMPATIBILITY_INFO" | grep -q 'CRYPTO_OK'); then
  273. ## failed to start, might be due to missing libs, try to be portable
  274. export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-$DYNLIBS_DIR}"
  275. if [ "$LD_LIBRARY_PATH" != "$DYNLIBS_DIR" ]; then
  276. export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
  277. fi
  278. ## Turn off debug, because COMPATIBILITY_INFO needs to capture stderr
  279. set +x
  280. COMPATIBILITY_INFO="$(compatiblity_info 2>&1 || true)"
  281. if ! (echo -e "$COMPATIBILITY_INFO" | grep -q 'BEAM_OK'); then
  282. ## not able to start beam.smp
  283. echoerr "$COMPATIBILITY_INFO"
  284. echoerr "Please ensure it is running on the correct platform:"
  285. echoerr "$BUILD_INFO"
  286. echoerr "Version=$REL_VSN"
  287. echoerr "Required dependencies: openssl-1.1.1 (libcrypto), libncurses and libatomic1"
  288. exit 1
  289. elif ! (echo -e "$COMPATIBILITY_INFO" | grep -q 'CRYPTO_OK'); then
  290. ## not able to start crypto app
  291. echoerr "$COMPATIBILITY_INFO"
  292. exit 2
  293. fi
  294. echoerr "Using libs from '${DYNLIBS_DIR}' due to missing from the OS."
  295. fi
  296. [ "$DEBUG" -eq 1 ] && set -x
  297. fi
  298. NO_EPMD="-start_epmd false -epmd_module ekka_epmd -proto_dist ekka"
  299. EPMD_ARGS="${EPMD_ARGS:-${NO_EPMD}}"
  300. # Warn the user if ulimit -n is less than 1024
  301. ULIMIT_F=$(ulimit -n)
  302. if [ "$ULIMIT_F" -lt 1024 ]; then
  303. echo "!!!!"
  304. echo "!!!! WARNING: ulimit -n is ${ULIMIT_F}; 1024 is the recommended minimum."
  305. echo "!!!!"
  306. fi
  307. SED_REPLACE="sed -i "
  308. case $(sed --help 2>&1) in
  309. *GNU*) SED_REPLACE="sed -i ";;
  310. *BusyBox*) SED_REPLACE="sed -i ";;
  311. *) SED_REPLACE="sed -i '' ";;
  312. esac
  313. # Get node pid
  314. relx_get_pid() {
  315. if output="$(relx_nodetool rpcterms os getpid)"
  316. then
  317. # shellcheck disable=SC2001 # Escaped quote taken as closing quote in editor
  318. echo "$output" | sed -e 's/"//g'
  319. return 0
  320. else
  321. echo "$output"
  322. return 1
  323. fi
  324. }
  325. # Connect to a remote node
  326. remsh() {
  327. # Generate a unique id used to allow multiple remsh to the same node
  328. # transparently
  329. id="remsh$(relx_gen_id)-${NAME}"
  330. # Get the node's ticktime so that we use the same thing.
  331. TICKTIME="$(relx_nodetool rpcterms net_kernel get_net_ticktime)"
  332. # shellcheck disable=SC2086
  333. # Setup remote shell command to control node
  334. if [ "$IS_ELIXIR" = no ] || [ "${EMQX_CONSOLE_FLAVOR:-}" = 'erl' ] ; then
  335. set -- "$BINDIR/erl" "$NAME_TYPE" "$id" \
  336. -remsh "$NAME" -boot "$REL_DIR/start_clean" \
  337. -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
  338. -boot_var RELEASE_LIB "$ERTS_LIB_DIR" \
  339. -setcookie "$COOKIE" \
  340. -hidden \
  341. -kernel net_ticktime "$TICKTIME" \
  342. $EPMD_ARGS
  343. else
  344. set -- "$REL_DIR/iex" \
  345. --remsh "$NAME" \
  346. --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
  347. --cookie "$COOKIE" \
  348. --hidden \
  349. --erl "-kernel net_ticktime $TICKTIME" \
  350. --erl "$EPMD_ARGS" \
  351. --erl "$NAME_TYPE $id" \
  352. --boot "$REL_DIR/start_clean"
  353. fi
  354. exec "$@"
  355. }
  356. # Generate a random id
  357. relx_gen_id() {
  358. od -t x -N 4 /dev/urandom | head -n1 | awk '{print $2}'
  359. }
  360. call_nodetool() {
  361. "$ERTS_DIR/bin/escript" "$RUNNER_ROOT_DIR/bin/nodetool" "$@"
  362. }
  363. # Control a node
  364. relx_nodetool() {
  365. command="$1"; shift
  366. ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
  367. call_nodetool "$NAME_TYPE" "$NAME" \
  368. -setcookie "$COOKIE" "$command" "$@"
  369. }
  370. call_hocon() {
  371. call_nodetool hocon "$@" \
  372. || die "call_hocon_failed: $*" $?
  373. }
  374. ## Resolve boot configs in a batch
  375. ## This is because starting the Erlang beam with all modules loaded
  376. ## and parsing HOCON config + environment variables is a non-trivial task
  377. CONF_KEYS=( 'node.data_dir' 'node.name' 'node.cookie' 'node.db_backend' )
  378. if [ "$IS_ENTERPRISE" = 'yes' ]; then
  379. CONF_KEYS+=( 'license.file' 'license.key' )
  380. fi
  381. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  382. if [ "${EMQX_BOOT_CONFIGS:-}" = '' ]; then
  383. EMQX_BOOT_CONFIGS="$(call_hocon -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf multi_get "${CONF_KEYS[@]}")"
  384. ## export here so the 'console' command recursively called from
  385. ## 'start' command does not have to parse the configs again
  386. export EMQX_BOOT_CONFIGS
  387. fi
  388. else
  389. # For non-boot commands, we try to get data_dir from ps -ef command
  390. # shellcheck disable=SC2009
  391. PS_LINE="$(ps -ef | grep "\-[r]oot $RUNNER_ROOT_DIR" | grep -oE "\-emqx_data_dir.*"|| true)"
  392. if [ "$(echo -e "$PS_LINE" | wc -l)" -eq 1 ]; then
  393. ## only one emqx node is running
  394. ## strip 'emqx_data_dir ' and ' --' because the dir in between may contain spaces
  395. DATA_DIR="$(echo -e "$PS_LINE" | sed -E 's#.+emqx_data_dir[[:blank:]]##g' | sed -E 's#[[:blank:]]--$##g')"
  396. if [ "$DATA_DIR" = '' ]; then
  397. ## this should not happen unless -emqx_data_dir is not set
  398. die "node_is_not_running!" 1
  399. fi
  400. EMQX_BOOT_CONFIGS="node.data_dir=$DATA_DIR"
  401. else
  402. ## None or more than one node is running, resolve from boot config
  403. EMQX_BOOT_CONFIGS="$(call_hocon -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf multi_get "${CONF_KEYS[@]}")"
  404. fi
  405. fi
  406. get_boot_config() {
  407. path_to_value="$1"
  408. echo -e "$EMQX_BOOT_CONFIGS" | grep "$path_to_value=" | sed -e "s/$path_to_value=//g" | tr -d \"
  409. }
  410. DATA_DIR="$(get_boot_config 'node.data_dir')"
  411. # ensure no trailing /
  412. DATA_DIR="${DATA_DIR%/}"
  413. if [[ $DATA_DIR != /* ]]; then
  414. # relative path
  415. DATA_DIR="${RUNNER_ROOT_DIR}/${DATA_DIR}"
  416. fi
  417. CONFIGS_DIR="$DATA_DIR/configs"
  418. mkdir -p "$CONFIGS_DIR"
  419. check_license() {
  420. if [ "$IS_ENTERPRISE" == "no" ]; then
  421. return 0
  422. fi
  423. file_license="${EMQX_LICENSE__FILE:-$(get_boot_config 'license.file')}"
  424. if [[ -n "$file_license" && ("$file_license" != "undefined") ]]; then
  425. call_nodetool check_license_file "$file_license"
  426. else
  427. key_license="${EMQX_LICENSE__KEY:-$(get_boot_config 'license.key')}"
  428. if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then
  429. call_nodetool check_license_key "$key_license"
  430. else
  431. echoerr "License not found."
  432. echoerr "Please specify one via EMQX_LICENSE__KEY or EMQX_LICENSE__FILE variables"
  433. echoerr "or via license.key|file in emqx_enterprise.conf."
  434. return 1
  435. fi
  436. fi
  437. }
  438. # Run an escript in the node's environment
  439. relx_escript() {
  440. shift; scriptpath="$1"; shift
  441. "$ERTS_DIR/bin/escript" "$RUNNER_ROOT_DIR/$scriptpath" "$@"
  442. }
  443. # Output a start command for the last argument of run_erl
  444. relx_start_command() {
  445. printf "exec \"%s\" \"%s\"" "$RUNNER_SCRIPT" \
  446. "$START_OPTION"
  447. }
  448. # Function to generate app.config and vm.args
  449. # sets two environment variables CONF_FILE and ARGS_FILE
  450. generate_config() {
  451. local name_type="$1"
  452. local node_name="$2"
  453. ## Delete the *.siz files first or it can't start after
  454. ## changing the config 'log.rotation.size'
  455. rm -rf "${RUNNER_LOG_DIR}"/*.siz
  456. ## timestamp for each generation
  457. local NOW_TIME
  458. NOW_TIME="$(date +'%Y.%m.%d.%H.%M.%S')"
  459. ## this command populates two files: app.<time>.config and vm.<time>.args
  460. ## NOTE: the generate command merges environment variables to the base config (emqx.conf),
  461. ## but does not include the cluster-override.conf and local-override.conf
  462. ## meaning, certain overrides will not be mapped to app.<time>.config file
  463. call_hocon -v -t "$NOW_TIME" -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf -d "$DATA_DIR"/configs generate
  464. ## filenames are per-hocon convention
  465. CONF_FILE="$CONFIGS_DIR/app.$NOW_TIME.config"
  466. ARGS_FILE="$CONFIGS_DIR/vm.$NOW_TIME.args"
  467. ## Merge hocon generated *.args into the vm.args
  468. TMP_ARG_FILE="$CONFIGS_DIR/vm.args.tmp"
  469. cp "$EMQX_ETC_DIR/vm.args" "$TMP_ARG_FILE"
  470. echo "" >> "$TMP_ARG_FILE"
  471. echo "-pa \"${REL_DIR}/consolidated\"" >> "$TMP_ARG_FILE"
  472. ## read lines from generated vm.<time>.args file
  473. ## drop comment lines, and empty lines using sed
  474. ## pipe the lines to a while loop
  475. sed '/^#/d' "$ARGS_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
  476. ## in the loop, split the 'key[:space:]value' pair
  477. ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
  478. ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
  479. ## use the key to look up in vm.args file for the value
  480. TMP_ARG_VALUE=$(grep "^$ARG_KEY" "$TMP_ARG_FILE" || true | awk '{print $NF}')
  481. ## compare generated (to override) value to original (to be overridden) value
  482. if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
  483. ## if they are different
  484. if [ -n "$TMP_ARG_VALUE" ]; then
  485. ## if the old value is present, replace it with generated value
  486. sh -c "$SED_REPLACE 's|^$ARG_KEY.*$|$ARG_LINE|' \"$TMP_ARG_FILE\""
  487. else
  488. ## otherwise append generated value to the end
  489. echo "$ARG_LINE" >> "$TMP_ARG_FILE"
  490. fi
  491. fi
  492. done
  493. echo "$name_type $node_name" >> "$TMP_ARG_FILE"
  494. echo "-mnesia dir '\"$DATA_DIR/mnesia/$NAME\"'" >> "$TMP_ARG_FILE"
  495. ## rename the generated vm.<time>.args file
  496. mv -f "$TMP_ARG_FILE" "$ARGS_FILE"
  497. }
  498. # check if a PID is down
  499. is_down() {
  500. PID="$1"
  501. if ps -p "$PID" >/dev/null; then
  502. # still around
  503. # shellcheck disable=SC2009 # this grep pattern is not a part of the progra names
  504. if ps -p "$PID" | grep -q 'defunct'; then
  505. # zombie state, print parent pid
  506. parent="$(ps -o ppid= -p "$PID" | tr -d ' ')"
  507. echo "WARN: $PID is marked <defunct>, parent:"
  508. ps -p "$parent"
  509. return 0
  510. fi
  511. return 1
  512. fi
  513. # it's gone
  514. return 0
  515. }
  516. wait_for() {
  517. local WAIT_TIME
  518. local CMD
  519. WAIT_TIME="$1"
  520. shift
  521. CMD="$*"
  522. while true; do
  523. if $CMD >/dev/null 2>&1; then
  524. return 0
  525. fi
  526. if [ "$WAIT_TIME" -le 0 ]; then
  527. return 1
  528. fi
  529. WAIT_TIME=$((WAIT_TIME - 1))
  530. sleep 1
  531. done
  532. }
  533. wait_until_return_val() {
  534. local RESULT
  535. local WAIT_TIME
  536. local CMD
  537. RESULT="$1"
  538. WAIT_TIME="$2"
  539. shift 2
  540. CMD="$*"
  541. while true; do
  542. if [ "$($CMD 2>/dev/null)" = "$RESULT" ]; then
  543. return 0
  544. fi
  545. if [ "$WAIT_TIME" -le 0 ]; then
  546. return 1
  547. fi
  548. WAIT_TIME=$((WAIT_TIME - 1))
  549. sleep 1
  550. done
  551. }
  552. latest_vm_args() {
  553. local hint_var_name="$1"
  554. local vm_args_file
  555. vm_args_file="$(find "$CONFIGS_DIR" -type f -name "vm.*.args" | sort | tail -1)"
  556. if [ -f "$vm_args_file" ]; then
  557. echo "$vm_args_file"
  558. else
  559. set +x
  560. echoerr "Node not initialized?"
  561. echoerr "Generated config file vm.*.args is not found for command '$COMMAND'"
  562. echoerr "in config dir: $CONFIGS_DIR"
  563. echoerr "In case the file has been deleted while the node is running,"
  564. echoerr "set environment variable '$hint_var_name' to continue"
  565. exit 1
  566. fi
  567. }
  568. # backward compabible with 4.x
  569. tr_log_to_env() {
  570. local log_to=${EMQX_LOG__TO:-undefined}
  571. # unset because it's unknown to 5.0
  572. unset EMQX_LOG__TO
  573. case "${log_to}" in
  574. console)
  575. export EMQX_LOG__CONSOLE_HANDLER__ENABLE='true'
  576. export EMQX_LOG__FILE_HANDLERS__DEFAULT__ENABLE='false'
  577. ;;
  578. file)
  579. export EMQX_LOG__CONSOLE_HANDLER__ENABLE='false'
  580. export EMQX_LOG__FILE_HANDLERS__DEFAULT__ENABLE='true'
  581. ;;
  582. both)
  583. export EMQX_LOG__CONSOLE_HANDLER__ENABLE='true'
  584. export EMQX_LOG__FILE_HANDLERS__DEFAULT__ENABLE='true'
  585. ;;
  586. default)
  587. # want to use config file defaults, do nothing
  588. ;;
  589. undefined)
  590. # value not set, do nothing
  591. ;;
  592. *)
  593. echoerr "Unknown environment value for EMQX_LOG__TO=${log_to} discarded"
  594. ;;
  595. esac
  596. }
  597. maybe_log_to_console() {
  598. if [ "${EMQX_LOG__TO:-}" = 'default' ]; then
  599. # want to use config file defaults, do nothing
  600. unset EMQX_LOG__TO
  601. else
  602. tr_log_to_env
  603. # ensure defaults
  604. export EMQX_LOG__CONSOLE_HANDLER__ENABLE="${EMQX_LOG__CONSOLE_HANDLER__ENABLE:-true}"
  605. export EMQX_LOG__FILE_HANDLERS__DEFAULT__ENABLE="${EMQX_LOG__FILE_HANDLERS__DEFAULT__ENABLE:-false}"
  606. fi
  607. }
  608. if [ -n "${EMQX_NODE_NAME:-}" ]; then
  609. export EMQX_NODE__NAME="${EMQX_NODE_NAME}"
  610. unset EMQX_NODE_NAME
  611. fi
  612. ## Possible ways to configure emqx node name:
  613. ## 1. configure node.name in emqx.conf
  614. ## 2. override with environment variable EMQX_NODE__NAME
  615. ## Node name is either short-name (without '@'), e.g. 'emqx'
  616. ## or long name (with '@') e.g. 'emqx@example.net' or 'emqx@127.0.0.1'
  617. NAME="${EMQX_NODE__NAME:-}"
  618. if [ -z "$NAME" ]; then
  619. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  620. # for boot commands, inspect emqx.conf for node name
  621. NAME="$(get_boot_config 'node.name')"
  622. else
  623. vm_args_file="$(latest_vm_args 'EMQX_NODE__NAME')"
  624. NAME="$(grep -E '^-s?name' "${vm_args_file}" | awk '{print $2}')"
  625. fi
  626. fi
  627. # force to use 'emqx' short name
  628. [ -z "$NAME" ] && NAME='emqx'
  629. case "$NAME" in
  630. *@*)
  631. NAME_TYPE='-name'
  632. ;;
  633. *)
  634. NAME_TYPE='-sname'
  635. esac
  636. SHORT_NAME="$(echo "$NAME" | awk -F'@' '{print $1}')"
  637. if ! (echo "$SHORT_NAME" | grep -q '^[0-9A-Za-z_\-]\+$'); then
  638. echo "Invalid node name, should be of format '^[0-9A-Za-z_-]+$'."
  639. exit 1
  640. fi
  641. # This also changes the program name from 'beam.smp' to node name
  642. # e.g. the 'ps' command output
  643. export ESCRIPT_NAME="$SHORT_NAME"
  644. PIPE_DIR="${PIPE_DIR:-/$DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}"
  645. ## make EMQX_NODE_COOKIE right
  646. if [ -n "${EMQX_NODE_COOKIE:-}" ]; then
  647. export EMQX_NODE__COOKIE="${EMQX_NODE_COOKIE}"
  648. unset EMQX_NODE_COOKIE
  649. fi
  650. COOKIE="${EMQX_NODE__COOKIE:-}"
  651. if [ -z "$COOKIE" ]; then
  652. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  653. COOKIE="$(get_boot_config 'node.cookie')"
  654. else
  655. vm_args_file="$(latest_vm_args 'EMQX_NODE__COOKIE')"
  656. COOKIE="$(grep -E '^-setcookie' "${vm_args_file}" | awk '{print $2}')"
  657. fi
  658. fi
  659. if [ -z "$COOKIE" ]; then
  660. die "Please set node.cookie in $EMQX_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE__COOKIE"
  661. fi
  662. ## check if OTP version has mnesia_hook feature; if not, fallback to
  663. ## using Mnesia DB backend.
  664. if [[ "$IS_BOOT_COMMAND" == 'yes' && "$(get_boot_config 'node.db_backend')" == "rlog" ]]; then
  665. if ! (echo -e "$COMPATIBILITY_INFO" | grep -q 'MNESIA_OK'); then
  666. echoerr "DB Backend is RLOG, but an incompatible OTP version has been detected. Falling back to using Mnesia DB backend."
  667. export EMQX_NODE__DB_BACKEND=mnesia
  668. export EMQX_NODE__DB_ROLE=core
  669. fi
  670. fi
  671. cd "$RUNNER_ROOT_DIR"
  672. case "${COMMAND}" in
  673. start)
  674. # Make sure a node IS not running
  675. if relx_nodetool "ping" >/dev/null 2>&1; then
  676. die "node_is_already_running!"
  677. fi
  678. # this flag passes down to console mode
  679. # so we know it's intended to be run in daemon mode
  680. export _EMQX_START_DAEMON_MODE=1
  681. case "$COMMAND" in
  682. start)
  683. shift
  684. START_OPTION="console"
  685. HEART_OPTION="start"
  686. ;;
  687. esac
  688. RUN_PARAM="$*"
  689. # Set arguments for the heart command
  690. set -- "$RUNNER_SCRIPT" "$HEART_OPTION"
  691. [ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM"
  692. # Export the HEART_COMMAND
  693. HEART_COMMAND="$RUNNER_SCRIPT $COMMAND"
  694. export HEART_COMMAND
  695. ## See: http://erlang.org/doc/man/run_erl.html
  696. # Export the RUN_ERL_LOG_GENERATIONS
  697. export RUN_ERL_LOG_GENERATIONS=${RUN_ERL_LOG_GENERATIONS:-"5"}
  698. # Export the RUN_ERL_LOG_MAXSIZE
  699. export RUN_ERL_LOG_MAXSIZE=${RUN_ERL_LOG_MAXSIZE:-"10485760"}
  700. mkdir -p "$PIPE_DIR"
  701. "$BINDIR/run_erl" -daemon "$PIPE_DIR" "$RUNNER_LOG_DIR" \
  702. "$(relx_start_command)"
  703. WAIT_TIME=${EMQX_WAIT_FOR_START:-120}
  704. if wait_until_return_val "true" "$WAIT_TIME" 'relx_nodetool' \
  705. 'eval' 'emqx:is_running()'; then
  706. echo "$EMQX_DESCRIPTION $REL_VSN is started successfully!"
  707. exit 0
  708. else
  709. echo "$EMQX_DESCRIPTION $REL_VSN failed to start in ${WAIT_TIME} seconds."
  710. echo "Please find more information in erlang.log.N"
  711. echo "Or run 'env DEBUG=1 $0 console' to have logs printed to console."
  712. exit 1
  713. fi
  714. ;;
  715. stop)
  716. # Wait for the node to completely stop...
  717. PID="$(relx_get_pid)"
  718. if ! relx_nodetool "stop"; then
  719. die "Graceful shutdown failed PID=[$PID]"
  720. fi
  721. WAIT_TIME="${EMQX_WAIT_FOR_STOP:-120}"
  722. if ! wait_for "$WAIT_TIME" 'is_down' "$PID"; then
  723. msg="dangling after ${WAIT_TIME} seconds"
  724. # also log to syslog
  725. logger -t "${REL_NAME}[${PID}]" "STOP: $msg"
  726. # log to user console
  727. echoerr "Stop failed, $msg"
  728. echo "ERROR: $PID is still around"
  729. ps -p "$PID"
  730. exit 1
  731. fi
  732. echo "ok"
  733. logger -t "${REL_NAME}[${PID}]" "STOP: OK"
  734. ;;
  735. pid)
  736. ## Get the VM's pid
  737. if ! relx_get_pid; then
  738. exit 1
  739. fi
  740. ;;
  741. ping)
  742. assert_node_alive
  743. echo pong
  744. ;;
  745. escript)
  746. ## Run an escript under the node's environment
  747. if ! relx_escript "$@"; then
  748. exit 1
  749. fi
  750. ;;
  751. attach)
  752. assert_node_alive
  753. shift
  754. exec "$BINDIR/to_erl" "$PIPE_DIR"
  755. ;;
  756. remote_console)
  757. assert_node_alive
  758. shift
  759. remsh
  760. ;;
  761. upgrade|downgrade|install|unpack|uninstall)
  762. if [ -z "${2:-}" ]; then
  763. echo "Missing version argument"
  764. echo "Usage: $REL_NAME $COMMAND {version}"
  765. exit 1
  766. fi
  767. shift
  768. assert_node_alive
  769. ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
  770. exec "$BINDIR/escript" "$RUNNER_ROOT_DIR/bin/install_upgrade.escript" \
  771. "$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
  772. ;;
  773. versions)
  774. assert_node_alive
  775. shift
  776. ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
  777. exec "$BINDIR/escript" "$RUNNER_ROOT_DIR/bin/install_upgrade.escript" \
  778. "versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
  779. ;;
  780. console|console_clean|foreground)
  781. # .boot file typically just $REL_NAME (ie, the app name)
  782. # however, for debugging, sometimes start_clean.boot is useful.
  783. # For e.g. 'setup', one may even want to name another boot script.
  784. case "$COMMAND" in
  785. console|foreground)
  786. if [ -f "$REL_DIR/$REL_NAME.boot" ]; then
  787. BOOTFILE="$REL_DIR/$REL_NAME"
  788. else
  789. BOOTFILE="$REL_DIR/start"
  790. fi
  791. ;;
  792. console_clean)
  793. BOOTFILE="$REL_DIR/start_clean"
  794. ;;
  795. esac
  796. case "$COMMAND" in
  797. foreground)
  798. FOREGROUNDOPTIONS="-noshell -noinput +Bd"
  799. ;;
  800. *)
  801. FOREGROUNDOPTIONS=''
  802. ;;
  803. esac
  804. # set before generate_config
  805. if [ "${_EMQX_START_DAEMON_MODE:-}" = 1 ]; then
  806. tr_log_to_env
  807. else
  808. maybe_log_to_console
  809. fi
  810. #generate app.config and vm.args
  811. generate_config "$NAME_TYPE" "$NAME"
  812. check_license
  813. # Setup beam-required vars
  814. EMU="beam"
  815. PROGNAME="${0}"
  816. export EMU
  817. export PROGNAME
  818. # Store passed arguments since they will be erased by `set`
  819. # add emqx_data_dir to boot command so it is visible from 'ps -ef'
  820. ARGS="$*"
  821. # shellcheck disable=SC2086
  822. # Build an array of arguments to pass to exec later on
  823. # Build it here because this command will be used for logging.
  824. if [ "$IS_ELIXIR" = no ] || [ "${EMQX_CONSOLE_FLAVOR:-}" = 'erl' ] ; then
  825. # pass down RELEASE_LIB so we can switch to IS_ELIXIR=no
  826. # to boot an Erlang node from the elixir release
  827. set -- "$BINDIR/erlexec" \
  828. $FOREGROUNDOPTIONS \
  829. -boot "$BOOTFILE" \
  830. -boot_var RELEASE_LIB "$ERTS_LIB_DIR" \
  831. -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
  832. -mode "$CODE_LOADING_MODE" \
  833. -config "$CONF_FILE" \
  834. -args_file "$ARGS_FILE" \
  835. $EPMD_ARGS
  836. else
  837. set -- "$REL_DIR/iex" \
  838. --boot "$BOOTFILE" \
  839. --boot-var RELEASE_LIB "${ERTS_LIB_DIR}" \
  840. --erl-config "${CONF_FILE}" \
  841. --vm-args "${ARGS_FILE}" \
  842. --erl "$FOREGROUNDOPTIONS" \
  843. --erl "-mode $CODE_LOADING_MODE" \
  844. --erl "$EPMD_ARGS" \
  845. --werl
  846. fi
  847. # Log the startup
  848. logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS} -emqx_data_dir ${DATA_DIR}"
  849. # Start the VM
  850. exec "$@" -- ${1+$ARGS} -emqx_data_dir "${DATA_DIR}"
  851. ;;
  852. ctl)
  853. assert_node_alive
  854. shift
  855. relx_nodetool rpc_infinity emqx_ctl run_command "$@"
  856. ;;
  857. rpc)
  858. assert_node_alive
  859. shift
  860. relx_nodetool rpc "$@"
  861. ;;
  862. rpcterms)
  863. assert_node_alive
  864. shift
  865. relx_nodetool rpcterms "$@"
  866. ;;
  867. eval)
  868. assert_node_alive
  869. shift
  870. if [ "$IS_ELIXIR" = "yes" ]
  871. then
  872. "$REL_DIR/elixir" \
  873. --hidden \
  874. --name "rand-$(relx_gen_id)-$NAME" \
  875. --cookie "$COOKIE" \
  876. --boot "$REL_DIR/start_clean" \
  877. --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
  878. --vm-args "$REL_DIR/remote.vm.args" \
  879. --erl "-start_epmd false -epmd_module ekka_epmd" \
  880. --rpc-eval "$NAME" "$@"
  881. else
  882. relx_nodetool "eval" "$@"
  883. fi
  884. ;;
  885. eval-erl)
  886. assert_node_alive
  887. shift
  888. relx_nodetool "eval" "$@"
  889. ;;
  890. *)
  891. usage "$COMMAND"
  892. exit 1
  893. ;;
  894. esac
  895. exit 0