emqx 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  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. if [ "$DEBUG" -eq 1 ]; then
  7. set -x
  8. fi
  9. if [ "$DEBUG" -eq 2 ]; then
  10. set -x
  11. export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
  12. fi
  13. logerr() {
  14. if [ "${TERM:-dumb}" = dumb ]; then
  15. echo -e "ERROR: $*" 1>&2
  16. else
  17. echo -e "$(tput setaf 1)ERROR: $*$(tput sgr0)" 1>&2
  18. fi
  19. }
  20. logwarn() {
  21. if [ "${TERM:-dumb}" = dumb ]; then
  22. echo "WARNING: $*"
  23. else
  24. echo "$(tput setaf 3)WARNING: $*$(tput sgr0)"
  25. fi
  26. }
  27. loginfo() {
  28. if [ "${TERM:-dumb}" = dumb ]; then
  29. echo "INFO: $*"
  30. else
  31. echo "$(tput setaf 2)INFO: $*$(tput sgr0)"
  32. fi
  33. }
  34. logdebug() {
  35. if [ "$DEBUG" -eq 1 ]; then
  36. echo "DEBUG: $*"
  37. fi
  38. }
  39. die() {
  40. set +x
  41. logerr "$1"
  42. errno=${2:-1}
  43. exit "$errno"
  44. }
  45. # We need to find real directory with emqx files on all platforms
  46. # even when bin/emqx is symlinked on several levels
  47. # - readlink -f works perfectly, but `-f` flag has completely different meaning in BSD version,
  48. # so we can't use it universally.
  49. # - `stat -f%R` on MacOS does exactly what `readlink -f` does on Linux, but we can't use it
  50. # as a universal solution either because GNU stat has different syntax and this argument is invalid.
  51. # Also, version of stat which supports this syntax is only available since MacOS 12
  52. if [ "$(uname -s)" == 'Darwin' ]; then
  53. product_version="$(sw_vers -productVersion | cut -d '.' -f 1)"
  54. if [ "$product_version" -ge 12 ]; then
  55. # if homebrew coreutils package is installed, GNU version of stat can take precedence,
  56. # so we use absolute path to ensure we are calling MacOS default
  57. RUNNER_ROOT_DIR="$(cd "$(dirname "$(/usr/bin/stat -f%R "$0" || echo "$0")")"/..; pwd -P)"
  58. else
  59. # try our best to resolve link on MacOS <= 11
  60. RUNNER_ROOT_DIR="$(cd "$(dirname "$(readlink "$0" || echo "$0")")"/..; pwd -P)"
  61. fi
  62. else
  63. RUNNER_ROOT_DIR="$(cd "$(dirname "$(realpath "$0" || echo "$0")")"/..; pwd -P)"
  64. fi
  65. COMMAND="${1:-}"
  66. GREP='grep --color=never'
  67. if [ -z "$COMMAND" ]; then
  68. usage 'help'
  69. exit 1
  70. elif [ "$COMMAND" = 'help' ]; then
  71. usage 'help'
  72. exit 0
  73. fi
  74. if [ "${2:-}" = 'help' ]; then
  75. ## 'ctl' command has its own usage info
  76. if [ "$COMMAND" != 'ctl' ]; then
  77. usage "$COMMAND"
  78. exit 0
  79. fi
  80. fi
  81. ## IS_BOOT_COMMAND is set for later to inspect node name and cookie from hocon config (or env variable)
  82. case "${COMMAND}" in
  83. start|console|console_clean|foreground|check_config)
  84. IS_BOOT_COMMAND='yes'
  85. ;;
  86. ertspath)
  87. echo "$ERTS_DIR"
  88. exit 0
  89. ;;
  90. root_dir)
  91. echo "$RUNNER_ROOT_DIR"
  92. exit 0
  93. ;;
  94. *)
  95. IS_BOOT_COMMAND='no'
  96. ;;
  97. esac
  98. RELUP_DIR="relup"
  99. BASE_RUNNER_ROOT_DIR="${BASE_RUNNER_ROOT_DIR:-$RUNNER_ROOT_DIR}"
  100. RELUP_PATH="$RUNNER_ROOT_DIR/$RELUP_DIR"
  101. if [ -f "$RELUP_PATH/version" ]; then
  102. TARGET_VSN=$(cat "$RELUP_PATH/version")
  103. export BASE_RUNNER_ROOT_DIR
  104. ## only print for boot commands to avoid messing the CLI outputs
  105. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  106. loginfo "Loading emqx from hot-upgrade dir: $RELUP_PATH"
  107. fi
  108. exec "$RELUP_PATH/$TARGET_VSN"/bin/emqx "$@"
  109. fi
  110. # shellcheck disable=SC1090,SC1091
  111. . "$RUNNER_ROOT_DIR"/releases/emqx_vars
  112. # defined in emqx_vars
  113. export RUNNER_ROOT_DIR
  114. export EMQX_ETC_DIR
  115. export REL_VSN
  116. export SCHEMA_MOD
  117. export IS_ENTERPRISE
  118. RUNNER_SCRIPT="$RUNNER_BIN_DIR/$REL_NAME"
  119. CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
  120. REL_DIR="$RUNNER_ROOT_DIR/releases/$REL_VSN"
  121. WHOAMI=$(whoami 2>/dev/null || id -u)
  122. # hocon try to read environment variables starting with "EMQX_"
  123. export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
  124. export ERTS_DIR="$RUNNER_ROOT_DIR/erts-$ERTS_VSN"
  125. export BINDIR="$ERTS_DIR/bin"
  126. export EMU="beam"
  127. export PROGNAME="erl"
  128. export ERTS_LIB_DIR="$RUNNER_ROOT_DIR/lib"
  129. DYNLIBS_DIR="$RUNNER_ROOT_DIR/dynlibs"
  130. assert_node_alive() {
  131. if ! relx_nodetool "ping" > /dev/null; then
  132. exit 1
  133. fi
  134. }
  135. usage() {
  136. local command="$1"
  137. case "$command" in
  138. start)
  139. echo "Start EMQX service in daemon mode"
  140. ;;
  141. stop)
  142. echo "Stop the running EMQX program"
  143. ;;
  144. console)
  145. echo "Boot up EMQX service in an interactive Erlang or Elixir shell"
  146. echo "This command needs a tty"
  147. ;;
  148. console_clean)
  149. echo "This command does NOT boot up the EMQX service"
  150. echo "It only starts an interactive Erlang or Elixir console with all the"
  151. echo "EMQX code available"
  152. ;;
  153. foreground)
  154. echo "Start EMQX in foreground mode without an interactive shell"
  155. ;;
  156. pid)
  157. echo "Print out EMQX process identifier"
  158. ;;
  159. ping)
  160. echo "Check if the EMQX node is up and running"
  161. echo "This command exit with 0 silently if node is running"
  162. ;;
  163. escript)
  164. echo "Execute a escript using the Erlang runtime from EMQX package installation"
  165. echo "For example $REL_NAME escript /path/to/my/escript my_arg1 my_arg2"
  166. ;;
  167. attach)
  168. echo "This command is applicable when EMQX is started in daemon mode."
  169. echo "It attaches the current shell to EMQX's control console"
  170. echo "through a named pipe."
  171. logwarn "try to use the safer alternative, remote_console command."
  172. ;;
  173. remote_console)
  174. echo "Start an interactive shell running an Erlang or Elixir node which "
  175. echo "hidden-connects to the running EMQX node".
  176. echo "This command is mostly used for troubleshooting."
  177. ;;
  178. ertspath)
  179. echo "Print path to Erlang runtime bin dir"
  180. ;;
  181. rpc)
  182. echo "Usage: $REL_NAME rpc MODULE FUNCTION [ARGS, ...]"
  183. echo "Connect to the EMQX node and make an Erlang RPC"
  184. echo "This command blocks for at most 60 seconds."
  185. echo "It exits with non-zero code in case of any RPC failure"
  186. echo "including connection error and runtime exception"
  187. ;;
  188. rpcterms)
  189. echo "Usage: $REL_NAME rpcterms MODULE FUNCTION [ARGS, ...]"
  190. echo "Connect to the EMQX node and make an Erlang RPC"
  191. echo "The result of the RPC call is pretty-printed as an "
  192. echo "Erlang term"
  193. ;;
  194. root_dir)
  195. echo "Print EMQX installation root dir"
  196. ;;
  197. eval)
  198. echo "Evaluate an Erlang expression in the EMQX node."
  199. ;;
  200. eval-ex)
  201. echo "Evaluate an Elixir expression in the EMQX node. Only applies to Elixir node"
  202. ;;
  203. versions)
  204. echo "List installed EMQX release versions and their status"
  205. ;;
  206. unpack)
  207. echo "Usage: $REL_NAME unpack [VERSION]"
  208. echo "Unpacks a release package VERSION, it assumes that this"
  209. echo "release package tarball has already been deployed at one"
  210. echo "of the following locations:"
  211. echo " releases/<relname>-<version>.tar.gz"
  212. ;;
  213. install)
  214. echo "Usage: $REL_NAME install [VERSION]"
  215. echo "Installs a release package VERSION, it assumes that this"
  216. echo "release package tarball has already been deployed at one"
  217. echo "of the following locations:"
  218. echo " releases/<relname>-<version>.tar.gz"
  219. echo ""
  220. echo " --no-permanent Install release package VERSION but"
  221. echo " don't make it permanent"
  222. ;;
  223. uninstall)
  224. echo "Usage: $REL_NAME uninstall [VERSION]"
  225. echo "Uninstalls a release VERSION, it will only accept"
  226. echo "versions that are not currently in use"
  227. ;;
  228. upgrade)
  229. echo "Usage: $REL_NAME upgrade [VERSION]"
  230. echo "Upgrades the currently running release to VERSION, it assumes"
  231. echo "that a release package tarball has already been deployed at one"
  232. echo "of the following locations:"
  233. echo " releases/<relname>-<version>.tar.gz"
  234. echo ""
  235. echo " --no-permanent Install release package VERSION but"
  236. echo " don't make it permanent"
  237. ;;
  238. downgrade)
  239. echo "Usage: $REL_NAME downgrade [VERSION]"
  240. echo "Downgrades the currently running release to VERSION, it assumes"
  241. echo "that a release package tarball has already been deployed at one"
  242. echo "of the following locations:"
  243. echo " releases/<relname>-<version>.tar.gz"
  244. echo ""
  245. echo " --no-permanent Install release package VERSION but"
  246. echo " don't make it permanent"
  247. ;;
  248. check_config)
  249. echo "Checks the EMQX config without generating any files"
  250. ;;
  251. *)
  252. echo "Usage: $REL_NAME COMMAND [help]"
  253. echo ''
  254. echo "Commonly used COMMANDs:"
  255. echo " start: Start EMQX in daemon mode"
  256. echo " console: Start EMQX in an interactive Erlang or Elixir shell"
  257. echo " foreground: Start EMQX in foreground mode without an interactive shell"
  258. echo " stop: Stop the running EMQX node"
  259. echo " ctl: Administration commands, execute '$REL_NAME ctl help' for more details"
  260. echo ''
  261. echo "More:"
  262. echo " Shell attach: remote_console | attach"
  263. # echo " Up/Down-grade: upgrade | downgrade | install | uninstall | versions" # TODO enable when supported
  264. echo " Install Info: ertspath | root_dir"
  265. echo " Runtime Status: pid | ping"
  266. echo " Validate Config: check_config"
  267. echo " Advanced: console_clean | escript | rpc | rpcterms | eval | eval-ex"
  268. echo ''
  269. echo "Execute '$REL_NAME COMMAND help' for more information"
  270. ;;
  271. esac
  272. }
  273. ## backward compatible
  274. if [ -d "$ERTS_DIR/lib" ]; then
  275. export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
  276. fi
  277. # Simple way to check the correct user and fail early
  278. check_user() {
  279. # Validate that the user running the script is the owner of the
  280. # RUN_DIR.
  281. if [ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]; then
  282. if [ "x$WHOAMI" != "xroot" ]; then
  283. echo "You need to be root or use sudo to run this command"
  284. exit 1
  285. fi
  286. CMD="DEBUG=$DEBUG \"$RUNNER_SCRIPT\" "
  287. for ARG in "$@"; do
  288. CMD="${CMD} \"$ARG\""
  289. done
  290. # This will drop privileges into the runner user
  291. # It exec's in a new shell and the current shell will exit
  292. exec su - "$RUNNER_USER" -c "$CMD"
  293. fi
  294. }
  295. # Make sure the user running this script is the owner and/or su to that user
  296. check_user "$@"
  297. ES=$?
  298. if [ "$ES" -ne 0 ]; then
  299. exit $ES
  300. fi
  301. # Make sure log directory exists
  302. mkdir -p "$EMQX_LOG_DIR"
  303. # turn off debug as this is static
  304. set +x
  305. COMPATIBILITY_CHECK='
  306. io:format("BEAM_OK~n", []),
  307. try
  308. [_|_] = L = crypto:info_lib(),
  309. io:format("CRYPTO_OK ~0p~n", [L])
  310. catch
  311. _ : _ ->
  312. %% so logger has the chance to log something
  313. timer:sleep(100),
  314. halt(1)
  315. end,
  316. try
  317. mnesia_hook:module_info(),
  318. io:format("MNESIA_OK~n", [])
  319. catch
  320. _ : _ ->
  321. io:format("WARNING: Mnesia app has no post-coommit hook support~n", []),
  322. halt(2)
  323. end,
  324. halt(0).
  325. '
  326. [[ "$DEBUG" -gt 0 ]] && set -x
  327. compatiblity_info() {
  328. # RELEASE_LIB is used by Elixir
  329. # set crash-dump bytes to zero to ensure no crash dump is generated when erl crashes
  330. env ERL_CRASH_DUMP_BYTES=0 "$BINDIR/$PROGNAME" \
  331. -noshell \
  332. +S 2 \
  333. +P 65536 \
  334. +Q 65536 \
  335. -boot "$REL_DIR/start_clean" \
  336. -boot_var RELEASE_LIB "$ERTS_LIB_DIR/lib" \
  337. -eval "$COMPATIBILITY_CHECK"
  338. }
  339. # Collect Erlang/OTP runtime sanity and compatibility in one go
  340. maybe_use_portable_dynlibs() {
  341. # Read BUILD_INFO early as the next commands may mess up the shell
  342. BUILD_INFO="$(cat "${REL_DIR}/BUILD_INFO")"
  343. COMPATIBILITY_INFO="$(compatiblity_info 2>/dev/null || true)"
  344. if ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'CRYPTO_OK'); then
  345. ## failed to start, might be due to missing libs, try to be portable
  346. export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-$DYNLIBS_DIR}"
  347. if [ "$LD_LIBRARY_PATH" != "$DYNLIBS_DIR" ]; then
  348. export LD_LIBRARY_PATH="$DYNLIBS_DIR:$LD_LIBRARY_PATH"
  349. fi
  350. ## Turn off debug, because COMPATIBILITY_INFO needs to capture stderr
  351. COMPATIBILITY_INFO="$(compatiblity_info 2>&1 || true)"
  352. if ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'BEAM_OK'); then
  353. ## not able to start beam.smp
  354. logerr "$COMPATIBILITY_INFO"
  355. logerr "Please ensure it is running on the correct platform:"
  356. logerr "$BUILD_INFO"
  357. logerr "Version=$REL_VSN"
  358. logerr "Required dependencies: openssl-1.1.1 (libcrypto), libncurses and libatomic1"
  359. exit 1
  360. elif ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'CRYPTO_OK'); then
  361. ## not able to start crypto app
  362. logerr "$COMPATIBILITY_INFO"
  363. exit 2
  364. fi
  365. logwarn "Using libs from '${DYNLIBS_DIR}' due to missing from the OS."
  366. fi
  367. }
  368. SED_REPLACE="sed -i "
  369. case $(sed --help 2>&1) in
  370. *GNU*) SED_REPLACE="sed -i ";;
  371. *BusyBox*) SED_REPLACE="sed -i ";;
  372. *) SED_REPLACE="sed -i '' ";;
  373. esac
  374. # Get node pid
  375. relx_get_pid() {
  376. if output="$(relx_nodetool rpcterms os getpid)"
  377. then
  378. # shellcheck disable=SC2001 # Escaped quote taken as closing quote in editor
  379. echo "$output" | sed -e 's/"//g'
  380. return 0
  381. else
  382. echo "$output"
  383. return 1
  384. fi
  385. }
  386. # Connect to a remote node
  387. remsh() {
  388. # Generate a unique id used to allow multiple remsh to the same node
  389. # transparently
  390. id="remsh$(gen_node_id)-${NAME}"
  391. # shellcheck disable=SC2086
  392. # Setup remote shell command to control node
  393. if [ "$IS_ELIXIR" = no ] || [ "${EMQX_CONSOLE_FLAVOR:-}" = 'erl' ] ; then
  394. set -- "$BINDIR/erl" "$NAME_TYPE" "$id" \
  395. -remsh "$NAME" -boot "$REL_DIR/start_clean" \
  396. -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
  397. -boot_var RELEASE_LIB "$ERTS_LIB_DIR" \
  398. -setcookie "$COOKIE" \
  399. -hidden \
  400. -kernel net_ticktime "$TICKTIME" \
  401. +P 65536 \
  402. +Q 65536 \
  403. +S 2 \
  404. $EPMD_ARGS
  405. else
  406. set -- "$REL_DIR/iex" \
  407. --remsh "$NAME" \
  408. --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
  409. --cookie "$COOKIE" \
  410. --hidden \
  411. --erl "-kernel net_ticktime $TICKTIME" \
  412. --erl "$EPMD_ARGS" \
  413. --erl "$NAME_TYPE $id" \
  414. --erl "+P 65536" \
  415. --erl "+Q 65536" \
  416. --erl "+S 2" \
  417. --boot "$REL_DIR/start_clean"
  418. fi
  419. exec "$@"
  420. }
  421. # Generate a random id
  422. gen_node_id() {
  423. od -t u -N 4 /dev/urandom | head -n1 | awk '{print $2 % 1000}'
  424. }
  425. call_nodetool() {
  426. "$ERTS_DIR/bin/escript" "$RUNNER_ROOT_DIR/bin/nodetool" "$@"
  427. }
  428. # Control a node
  429. relx_nodetool() {
  430. command="$1"; shift
  431. ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS -setcookie $COOKIE" \
  432. call_nodetool "$NAME_TYPE" "$NAME" "$command" "$@"
  433. }
  434. call_hocon() {
  435. call_nodetool hocon "$@" \
  436. || die "call_hocon_failed: $*" $?
  437. }
  438. check_emqx_process() {
  439. local rootdir="$1"
  440. ## Find the running node from 'ps -ef'
  441. ## * The grep args like '[e]mqx' but not 'emqx' is to avoid greping the grep command itself
  442. ## * The running 'remsh' and 'nodetool' processes must be excluded
  443. ps -ef | $GREP '[e]mqx' | $GREP -v -E '(remsh|nodetool)' | $GREP -oE "\-[r]oot ${rootdir}.*" || true
  444. }
  445. find_emqx_process() {
  446. ## Maybe the emqx has been hot upgraded and is still running from the base root_dir.
  447. ## So instead of searching RUNNER_ROOT_DIR, we only search for processes running
  448. ## from BASE_RUNNER_ROOT_DIR (which is either equal to RUNNER_ROOT_DIR or a
  449. ## parent directory of it).
  450. local rootdir="${BASE_RUNNER_ROOT_DIR}"
  451. if [ -n "${EMQX_NODE__NAME:-}" ]; then
  452. # if node name is provided, filter by node name
  453. check_emqx_process "${rootdir}" | $GREP -E "\s-s?name\s${EMQX_NODE__NAME}" || true
  454. else
  455. check_emqx_process "${rootdir}"
  456. fi
  457. }
  458. ## Resolve boot configs in a batch
  459. ## This is because starting the Erlang beam with all modules loaded
  460. ## and parsing HOCON config + environment variables is a non-trivial task
  461. CONF_KEYS=( 'node.data_dir' 'node.name' 'node.cookie' 'node.db_backend' 'cluster.proto_dist' 'node.dist_net_ticktime' )
  462. if [ "$IS_ENTERPRISE" = 'yes' ]; then
  463. CONF_KEYS+=( 'license.key' )
  464. fi
  465. ## To be backward compatible, read and then unset EMQX_NODE_NAME
  466. if [ -n "${EMQX_NODE_NAME:-}" ]; then
  467. export EMQX_NODE__NAME="${EMQX_NODE_NAME}"
  468. unset EMQX_NODE_NAME
  469. fi
  470. # Turn off debug as the ps output can be quite noisy
  471. set +x
  472. PS_LINE="$(find_emqx_process)"
  473. logdebug "PS_LINE=$PS_LINE"
  474. RUNNING_NODES_COUNT="$(echo -e "$PS_LINE" | sed '/^\s*$/d' | wc -l)"
  475. [ "$RUNNING_NODES_COUNT" -gt 1 ] && logdebug "More than one running node found: count=$RUNNING_NODES_COUNT"
  476. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  477. if [ "$RUNNING_NODES_COUNT" -gt 0 ] && [ "$COMMAND" != 'check_config' ]; then
  478. running_node_name=$(echo -e "$PS_LINE" | $GREP -oE "\s-s?name.*" | awk '{print $2}' || true)
  479. if [ -n "$running_node_name" ] && [ "$running_node_name" = "${EMQX_NODE__NAME:-}" ]; then
  480. echo "Node ${running_node_name} is already running!"
  481. exit 1
  482. fi
  483. fi
  484. [ -f "$EMQX_ETC_DIR"/emqx.conf ] || die "emqx.conf is not found in $EMQX_ETC_DIR" 1
  485. maybe_use_portable_dynlibs
  486. if [ "${EMQX_BOOT_CONFIGS:-}" = '' ]; then
  487. EMQX_BOOT_CONFIGS="$(call_hocon -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf multi_get "${CONF_KEYS[@]}")"
  488. ## export here so the 'console' command recursively called from
  489. ## 'start' command does not have to parse the configs again
  490. export EMQX_BOOT_CONFIGS
  491. fi
  492. else
  493. # For non-boot commands, we need below runtime facts to connect to the running node:
  494. # 1. The running node name;
  495. # 2. The Erlang cookie in use by the running node name;
  496. # 3. SSL options if the node is using TLS for Erlang distribution;
  497. # 4. Erlang kernel application's net_ticktime config.
  498. #
  499. # There are 3 sources of truth to get those runtime information.
  500. # Listed in the order of preference:
  501. # 1. The boot command (which can be inspected from 'ps -ef' command output)
  502. # 2. The generated vm.<time>.config file located in the dir pointed by 'node.data_dir'
  503. # 3. The bootstrap config 'etc/emqx.conf'
  504. #
  505. # If failed to read from source 1, the information is retrieved from source 3
  506. # i.e. source 2 is never used.
  507. #
  508. # NOTES:
  509. # * We should avoid getting runtime information with the 3rd approach because 'etc/emqx.conf' might
  510. # be updated after the node is started. e.g. If a user starts the node with name 'emqx@127.0.0.1'
  511. # then update the config in the file to 'node.name = "emqx@local.net"', after this change,
  512. # there would be no way stop the running node 'emqx@127.0.0.1', because 'emqx stop' command
  513. # would try to stop the new node instead.
  514. if [ "$RUNNING_NODES_COUNT" -eq 1 ]; then
  515. ## only one emqx node is running, get running args from 'ps -ef' output
  516. tmp_nodename=$(echo -e "$PS_LINE" | $GREP -oE "\s-s?name.*" | awk '{print $2}' || true)
  517. tmp_cookie=$(echo -e "$PS_LINE" | $GREP -oE "\s-setcookie.*" | awk '{print $2}' || true)
  518. tmp_proto_dist=$(echo -e "$PS_LINE" | $GREP -oE '\s-ekka_proto_dist.*' | awk '{print $2}' || echo 'inet_tcp')
  519. SSL_DIST_OPTFILE="$(echo -e "$PS_LINE" | $GREP -oE '\-ssl_dist_optfile\s.+\s' | awk '{print $2}' || true)"
  520. tmp_ticktime="$(echo -e "$PS_LINE" | $GREP -oE '\s-kernel\snet_ticktime\s.+\s' | awk '{print $3}' || true)"
  521. 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)"
  522. ## Make the format like what call_hocon multi_get prints out, but only need 4 args
  523. 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}"
  524. else
  525. if [ "$RUNNING_NODES_COUNT" -gt 1 ]; then
  526. if [ -z "${EMQX_NODE__NAME:-}" ]; then
  527. tmp_nodenames=$(echo -e "$PS_LINE" | $GREP -oE "\s-s?name.*" | awk '{print $2}' | tr '\n' ' ')
  528. logerr "More than one EMQX node found running (root dir: ${RUNNER_ROOT_DIR})"
  529. logerr "Running nodes: $tmp_nodenames"
  530. logerr "Make sure environment variable EMQX_NODE__NAME is set to indicate for which node this command is intended."
  531. exit 1
  532. fi
  533. else
  534. if [ -n "${EMQX_NODE__NAME:-}" ]; then
  535. die "Node $EMQX_NODE__NAME is not running?"
  536. fi
  537. fi
  538. ## We have no choice but to read the bootstrap config (with environment overrides available in the current shell)
  539. [ -f "$EMQX_ETC_DIR"/emqx.conf ] || die "emqx.conf is not found in $EMQX_ETC_DIR" 1
  540. maybe_use_portable_dynlibs
  541. EMQX_BOOT_CONFIGS="$(call_hocon -s "$SCHEMA_MOD" -c "$EMQX_ETC_DIR"/emqx.conf multi_get "${CONF_KEYS[@]}")"
  542. fi
  543. fi
  544. logdebug "EMQX_BOOT_CONFIGS: $EMQX_BOOT_CONFIGS"
  545. [[ "$DEBUG" -gt 0 ]] && set -x
  546. get_boot_config() {
  547. path_to_value="$1"
  548. echo -e "$EMQX_BOOT_CONFIGS" | $GREP "$path_to_value=" | sed -e "s/$path_to_value=//g" | tr -d \"
  549. }
  550. EPMD_ARGS="${EPMD_ARGS:-"-start_epmd false -epmd_module ekka_epmd -proto_dist ekka"}"
  551. PROTO_DIST="$(get_boot_config 'cluster.proto_dist' || true)"
  552. TICKTIME="$(get_boot_config 'node.dist_net_ticktime' || echo '120')"
  553. # this environment variable is required by ekka_dist module
  554. # because proto_dist is overriden to ekka, and there is a lack of ekka_tls module
  555. export EKKA_PROTO_DIST_MOD="${PROTO_DIST:-inet_tcp}"
  556. if [ "$EKKA_PROTO_DIST_MOD" = 'inet_tls' ] || [ "$EKKA_PROTO_DIST_MOD" = 'inet6_tls' ]; then
  557. if [ "$IS_BOOT_COMMAND" = 'yes' ]; then
  558. SSL_DIST_OPTFILE=${EMQX_SSL_DIST_OPTFILE:-"$EMQX_ETC_DIR/ssl_dist.conf"}
  559. case "$SSL_DIST_OPTFILE" in
  560. *\ *)
  561. # there is unfortunately no way to support space for this option because we'd need to grep
  562. # from 'ps -ef' result to get this option for non-boot commands (nodetool) to run
  563. set +x
  564. logerr "Got space in: $SSL_DIST_OPTFILE"
  565. logerr "No space is allowed for Erlang distribution over SSL option file path."
  566. logerr "Configure it from environment variable EMQX_SSL_DIST_OPTFILE."
  567. logerr "Or make sure emqx root path '$RUNNER_ROOT_DIR' has no space"
  568. exit 1
  569. ;;
  570. *)
  571. true
  572. ;;
  573. esac
  574. fi
  575. EPMD_ARGS="${EPMD_ARGS} -ssl_dist_optfile $SSL_DIST_OPTFILE"
  576. fi
  577. DATA_DIR="$(get_boot_config 'node.data_dir')"
  578. # ensure no trailing /
  579. DATA_DIR="${DATA_DIR%/}"
  580. if [[ $DATA_DIR != /* ]]; then
  581. # relative path
  582. DATA_DIR="${BASE_RUNNER_ROOT_DIR}/${DATA_DIR}"
  583. fi
  584. CONFIGS_DIR="$DATA_DIR/configs"
  585. mkdir -p "$CONFIGS_DIR"
  586. check_license() {
  587. if [ "$IS_ENTERPRISE" == "no" ]; then
  588. return 0
  589. fi
  590. key_license="${EMQX_LICENSE__KEY:-$(get_boot_config 'license.key' || echo '')}"
  591. if [[ -n "$key_license" && ("$key_license" != "undefined") ]]; then
  592. call_nodetool check_license_key "$key_license"
  593. else
  594. set +x
  595. logerr "License not found."
  596. logerr "Please specify one via the EMQX_LICENSE__KEY variable"
  597. logerr "or via license.key in emqx.conf."
  598. return 1
  599. fi
  600. }
  601. # When deciding which install upgrade script to run, we have to check
  602. # our own version so we may avoid infinite loops and call the correct
  603. # version.
  604. current_script_version() {
  605. curr_script=$(basename "${BASH_SOURCE[0]}")
  606. suffix=${curr_script#*-}
  607. if [[ "${suffix}" == "${curr_script}" ]]; then
  608. # there's no suffix, so we're running the default `emqx` script;
  609. # we'll have to trust the REL_VSN variable
  610. echo "$REL_VSN"
  611. else
  612. echo "${suffix}"
  613. fi
  614. }
  615. parse_semver() {
  616. echo "$1" | tr '.|-' ' '
  617. }
  618. max_version_of() {
  619. local vsn1="$1"
  620. local vsn2="$2"
  621. echo "${vsn1}" "${vsn2}" | tr " " "\n" | sort -rV | head -n1
  622. }
  623. versioned_script_path() {
  624. local script_name="$1"
  625. local vsn="$2"
  626. echo "$RUNNER_ROOT_DIR/bin/$script_name-$vsn"
  627. }
  628. does_script_version_exist() {
  629. local script_name="$1"
  630. local vsn="$2"
  631. if [[ -f "$(versioned_script_path "$script_name" "$vsn")" ]]; then
  632. return 0
  633. else
  634. return 1
  635. fi
  636. }
  637. # extract_from_package packege_path destination file1 file2
  638. extract_from_package() {
  639. local package="$1"
  640. local dest_dir="$2"
  641. shift 2
  642. tar -C "$dest_dir" -xf "$package" "$@"
  643. }
  644. am_i_the_newest_script() {
  645. local curr_vsn other_vsn
  646. curr_vsn="$(current_script_version)"
  647. other_vsn="$1"
  648. max_vsn="$(max_version_of "$other_vsn" "$curr_vsn")"
  649. if [[ "$max_vsn" == "$curr_vsn" ]]; then
  650. return 0
  651. else
  652. return 1
  653. fi
  654. }
  655. locate_package() {
  656. local package_path candidates vsn
  657. vsn="$1"
  658. if [[ "${IS_ENTERPRISE}" == "yes" ]]; then
  659. package_pattern="$RUNNER_ROOT_DIR/releases/emqx-enterprise-$vsn-*.tar.gz"
  660. else
  661. package_pattern="$RUNNER_ROOT_DIR/releases/emqx-$vsn-*.tar.gz"
  662. fi
  663. # shellcheck disable=SC2207,SC2086
  664. candidates=($(ls $package_pattern))
  665. if [[ "${#candidates[@]}" == 0 ]]; then
  666. logerr "No package matching $package_pattern found."
  667. exit 1
  668. elif [[ "${#candidates[@]}" -gt 1 ]]; then
  669. logerr "Multiple packages matching $package_pattern found. Ensure only one exists."
  670. exit 1
  671. else
  672. echo "${candidates[0]}"
  673. fi
  674. }
  675. ensure_newest_script_is_extracted() {
  676. local newest_vsn="$1"
  677. local package_path tmpdir
  678. if does_script_version_exist "emqx" "$newest_vsn" \
  679. && does_script_version_exist "install_upgrade.escript" "$newest_vsn"; then
  680. return
  681. else
  682. package_path="$(locate_package "$newest_vsn")"
  683. tmpdir="$(mktemp -dp /tmp emqx.XXXXXXXXXXX)"
  684. extract_from_package \
  685. "$package_path" \
  686. "$tmpdir" \
  687. "bin/emqx-$newest_vsn" \
  688. "bin/install_upgrade.escript-$newest_vsn"
  689. cp "$tmpdir/bin/emqx-$newest_vsn" \
  690. "$tmpdir/bin/install_upgrade.escript-$newest_vsn" \
  691. "$RUNNER_ROOT_DIR/bin/"
  692. rm -rf "$tmpdir"
  693. fi
  694. }
  695. # Run an escript in the node's environment
  696. relx_escript() {
  697. shift; scriptpath="$1"; shift
  698. "$ERTS_DIR/bin/escript" "$RUNNER_ROOT_DIR/$scriptpath" "$@"
  699. }
  700. # Output a start command for the last argument of run_erl
  701. relx_start_command() {
  702. printf "exec \"%s\" \"%s\"" "$RUNNER_SCRIPT" \
  703. "$START_OPTION"
  704. }
  705. # Function to check configs without generating them
  706. check_config() {
  707. ## this command checks the configs without generating any files
  708. call_hocon -v \
  709. -s "$SCHEMA_MOD" \
  710. -c "$EMQX_ETC_DIR"/base.hocon \
  711. -c "$CONFIGS_DIR"/cluster.hocon \
  712. -c "$EMQX_ETC_DIR"/emqx.conf \
  713. check_schema
  714. }
  715. # Function to generate app.config and vm.args
  716. # sets two environment variables CONF_FILE and ARGS_FILE
  717. generate_config() {
  718. local name_type="$1"
  719. local node_name="$2"
  720. ## Delete the *.siz files first or it can't start after
  721. ## changing the config 'log.rotation.size'
  722. rm -f "${EMQX_LOG_DIR}"/*.siz
  723. ## timestamp for each generation
  724. local NOW_TIME
  725. NOW_TIME="$(date +'%Y.%m.%d.%H.%M.%S')"
  726. ## This command populates two files: app.<time>.config and vm.<time>.args
  727. ## It takes input sources and overlays values in below order:
  728. ## - etc/base.hocon
  729. ## - $CONFIGS_DIR/cluster.hocon
  730. ## - etc/emqx.conf
  731. ## - environment variables starts with EMQX_ e.g. EMQX_NODE__ROLE
  732. ##
  733. ## NOTE: it's a known issue that cluster.hocon may change right after the node boots up
  734. ## because it has to sync cluster.hocon from other nodes.
  735. call_hocon -v -t "$NOW_TIME" \
  736. -s "$SCHEMA_MOD" \
  737. -c "$EMQX_ETC_DIR"/base.hocon \
  738. -c "$CONFIGS_DIR"/cluster.hocon \
  739. -c "$EMQX_ETC_DIR"/emqx.conf \
  740. -d "$DATA_DIR"/configs generate
  741. ## filenames are per-hocon convention
  742. CONF_FILE="$CONFIGS_DIR/app.$NOW_TIME.config"
  743. ARGS_FILE="$CONFIGS_DIR/vm.$NOW_TIME.args"
  744. ## Merge hocon generated *.args into the vm.args
  745. TMP_ARG_FILE="$CONFIGS_DIR/vm.args.tmp"
  746. cp "$EMQX_ETC_DIR/vm.args" "$TMP_ARG_FILE"
  747. echo "" >> "$TMP_ARG_FILE"
  748. echo "-pa \"${REL_DIR}/consolidated\"" >> "$TMP_ARG_FILE"
  749. ## read lines from generated vm.<time>.args file
  750. ## drop comment lines, and empty lines using sed
  751. ## pipe the lines to a while loop
  752. sed '/^#/d' "$ARGS_FILE" | sed '/^$/d' | while IFS='' read -r ARG_LINE || [ -n "$ARG_LINE" ]; do
  753. ## in the loop, split the 'key[:space:]value' pair
  754. ARG_KEY=$(echo "$ARG_LINE" | awk '{$NF="";print}')
  755. ARG_VALUE=$(echo "$ARG_LINE" | awk '{print $NF}')
  756. ## use the key to look up in vm.args file for the value
  757. TMP_ARG_VALUE=$($GREP "^$ARG_KEY" "$TMP_ARG_FILE" || true | awk '{print $NF}')
  758. ## compare generated (to override) value to original (to be overridden) value
  759. if [ "$ARG_VALUE" != "$TMP_ARG_VALUE" ] ; then
  760. ## if they are different
  761. if [ -n "$TMP_ARG_VALUE" ]; then
  762. ## if the old value is present, replace it with generated value
  763. sh -c "$SED_REPLACE 's|^$ARG_KEY.*$|$ARG_LINE|' \"$TMP_ARG_FILE\""
  764. else
  765. ## otherwise append generated value to the end
  766. echo "$ARG_LINE" >> "$TMP_ARG_FILE"
  767. fi
  768. fi
  769. done
  770. echo "$name_type $node_name" >> "$TMP_ARG_FILE"
  771. echo "-mnesia dir '\"$DATA_DIR/mnesia/$NAME\"'" >> "$TMP_ARG_FILE"
  772. ## rename the generated vm.<time>.args file
  773. mv -f "$TMP_ARG_FILE" "$ARGS_FILE"
  774. }
  775. # check if a PID is defunct
  776. is_defunct() {
  777. local PID="$1"
  778. ps -fp "$PID" | $GREP -q 'defunct'
  779. }
  780. # check if a PID is down
  781. # shellcheck disable=SC2317 # call in func `nodetool_shutdown()`
  782. is_down() {
  783. PID="$1"
  784. if ps -p "$PID" >/dev/null; then
  785. # still around
  786. # shellcheck disable=SC2009 # this grep pattern is not a part of the program names
  787. if is_defunct "$PID"; then
  788. # zombie state, print parent pid
  789. parent="$(ps -o ppid= -p "$PID" | tr -d ' ')"
  790. if [ -z "$parent" ] && ! is_defunct "$PID"; then
  791. # process terminated in the meanwhile
  792. return 0;
  793. fi
  794. logwarn "$PID is marked <defunct>, parent: $(ps -p "$parent")"
  795. return 0
  796. fi
  797. return 1
  798. fi
  799. # it's gone
  800. return 0
  801. }
  802. wait_for() {
  803. local WAIT_TIME
  804. local CMD
  805. WAIT_TIME="$1"
  806. shift
  807. CMD="$*"
  808. while true; do
  809. if $CMD; then
  810. return 0
  811. fi
  812. if [ "$WAIT_TIME" -le 0 ]; then
  813. return 1
  814. fi
  815. WAIT_TIME=$((WAIT_TIME - 1))
  816. sleep 1
  817. done
  818. }
  819. wait_until_return_val() {
  820. local RESULT
  821. local WAIT_TIME
  822. local CMD
  823. RESULT="$1"
  824. WAIT_TIME="$2"
  825. shift 2
  826. CMD="$*"
  827. while true; do
  828. if [ "$($CMD 2>/dev/null)" = "$RESULT" ]; then
  829. return 0
  830. fi
  831. if [ "$WAIT_TIME" -le 0 ]; then
  832. return 1
  833. fi
  834. WAIT_TIME=$((WAIT_TIME - 1))
  835. sleep 1
  836. done
  837. }
  838. # First, there is EMQX_DEFAULT_LOG_HANDLER which can control the default values
  839. # to be used when generating configs.
  840. # It's set in docker entrypoint and in systemd service file.
  841. #
  842. # To be backward compatible with 4.x and v5.0.0 ~ v5.0.24/e5.0.2:
  843. # if EMQX_LOG__TO is set, we try to enable handlers from environment variables.
  844. # i.e. it overrides the default value set in EMQX_DEFAULT_LOG_HANDLER
  845. tr_log_to_env() {
  846. local log_to=${EMQX_LOG__TO:-undefined}
  847. # unset because it's unknown to 5.0
  848. unset EMQX_LOG__TO
  849. case "${log_to}" in
  850. console)
  851. export EMQX_LOG__CONSOLE__ENABLE='true'
  852. export EMQX_LOG__FILE__ENABLE='false'
  853. ;;
  854. file)
  855. export EMQX_LOG__CONSOLE__ENABLE='false'
  856. export EMQX_LOG__FILE__ENABLE='true'
  857. ;;
  858. both)
  859. export EMQX_LOG__CONSOLE__ENABLE='true'
  860. export EMQX_LOG__FILE__ENABLE='true'
  861. ;;
  862. default)
  863. # want to use config file defaults, do nothing
  864. ;;
  865. undefined)
  866. # value not set, do nothing
  867. ;;
  868. *)
  869. logerr "Unknown environment value for EMQX_LOG__TO=${log_to} discarded"
  870. ;;
  871. esac
  872. }
  873. maybe_log_to_console() {
  874. if [ "${EMQX_LOG__TO:-}" = 'default' ]; then
  875. # want to use defaults, do nothing
  876. unset EMQX_LOG__TO
  877. else
  878. tr_log_to_env
  879. export EMQX_DEFAULT_LOG_HANDLER=${EMQX_DEFAULT_LOG_HANDLER:-console}
  880. fi
  881. }
  882. # Warn the user if ulimit -n is less than 1024
  883. maybe_warn_ulimit() {
  884. ULIMIT_F=$(ulimit -n)
  885. if [ "$ULIMIT_F" -lt 1024 ]; then
  886. logwarn "ulimit -n is ${ULIMIT_F}; 1024 is the recommended minimum."
  887. fi
  888. }
  889. ## Possible ways to configure emqx node name:
  890. ## 1. configure node.name in emqx.conf
  891. ## 2. override with environment variable EMQX_NODE__NAME
  892. ## Node name is either short-name (without '@'), e.g. 'emqx'
  893. ## or long name (with '@') e.g. 'emqx@example.net' or 'emqx@127.0.0.1'
  894. NAME="${EMQX_NODE__NAME:-}"
  895. if [ -z "$NAME" ]; then
  896. NAME="$(get_boot_config 'node.name')"
  897. fi
  898. # force to use 'emqx' short name
  899. [ -z "$NAME" ] && NAME='emqx'
  900. case "$NAME" in
  901. *@*)
  902. NAME_TYPE='-name'
  903. ;;
  904. *)
  905. NAME_TYPE='-sname'
  906. esac
  907. SHORT_NAME="$(echo "$NAME" | awk -F'@' '{print $1}')"
  908. HOST_NAME="$(echo "$NAME" | awk -F'@' '{print $2}')"
  909. if ! (echo "$SHORT_NAME" | $GREP -q '^[0-9A-Za-z_\-]\+$'); then
  910. logerr "Invalid node name, should be of format '^[0-9A-Za-z_-]+$'."
  911. exit 1
  912. fi
  913. # This also changes the program name from 'beam.smp' to node name
  914. # e.g. the 'ps' command output
  915. export ESCRIPT_NAME="$SHORT_NAME"
  916. PIPE_DIR="${PIPE_DIR:-/$DATA_DIR/${WHOAMI}_erl_pipes/$NAME/}"
  917. ## Resolve Erlang cookie.
  918. if [ -n "${EMQX_NODE_COOKIE:-}" ]; then
  919. ## To be backward compatible, read and unset EMQX_NODE_COOKIE
  920. export EMQX_NODE__COOKIE="${EMQX_NODE_COOKIE}"
  921. unset EMQX_NODE_COOKIE
  922. fi
  923. COOKIE="${EMQX_NODE__COOKIE:-}"
  924. COOKIE_IN_USE="$(get_boot_config 'node.cookie')"
  925. if [ "$IS_BOOT_COMMAND" != 'yes' ] && [ -n "$COOKIE_IN_USE" ] && [ -n "$COOKIE" ] && [ "$COOKIE" != "$COOKIE_IN_USE" ]; then
  926. die "EMQX_NODE__COOKIE is different from the cookie used by $NAME"
  927. fi
  928. [ -z "$COOKIE" ] && COOKIE="$COOKIE_IN_USE"
  929. [ -z "$COOKIE" ] && COOKIE="$EMQX_DEFAULT_ERLANG_COOKIE"
  930. maybe_warn_default_cookie() {
  931. if [ $IS_BOOT_COMMAND = 'yes' ] && [ "$COOKIE" = "$EMQX_DEFAULT_ERLANG_COOKIE" ]; then
  932. logwarn "Default (insecure) Erlang cookie is in use."
  933. logwarn "Configure node.cookie in $EMQX_ETC_DIR/emqx.conf or override from environment variable EMQX_NODE__COOKIE"
  934. logwarn "NOTE: Use the same cookie for all nodes in the cluster."
  935. fi
  936. }
  937. ## check if OTP version has mnesia_hook feature; if not, fallback to
  938. ## using Mnesia DB backend.
  939. if [[ "$IS_BOOT_COMMAND" == 'yes' && "$(get_boot_config 'node.db_backend')" == "rlog" ]]; then
  940. if ! (echo -e "$COMPATIBILITY_INFO" | $GREP -q 'MNESIA_OK'); then
  941. logwarn "DB Backend is RLOG, but an incompatible OTP version has been detected. Falling back to using Mnesia DB backend."
  942. export EMQX_NODE__DB_BACKEND=mnesia
  943. export EMQX_NODE__ROLE=core
  944. fi
  945. fi
  946. diagnose_boot_failure_and_die() {
  947. local ps_line
  948. local app_status
  949. ps_line="$(find_emqx_process)"
  950. if [ -z "$ps_line" ]; then
  951. echo "Find more information in the latest log file: ${EMQX_LOG_DIR}/erlang.log.*"
  952. exit 1
  953. fi
  954. if ! relx_nodetool "ping" > /dev/null; then
  955. logerr "$NAME seems to be running, but not responding to pings."
  956. echo "Make sure '$HOST_NAME' is a resolvable and reachable hostname."
  957. pipe_shutdown
  958. exit 2
  959. fi
  960. app_status="$(relx_nodetool 'eval' 'emqx:is_running()')"
  961. if [ "$app_status" != 'true' ]; then
  962. logerr "$NAME node is started, but failed to complete the boot sequence in time."
  963. pipe_shutdown
  964. exit 3
  965. fi
  966. }
  967. ## Only works when started in daemon mode
  968. pipe_shutdown() {
  969. if [ -d "$PIPE_DIR" ]; then
  970. echo "Shutting down $NAME from to_erl pipe."
  971. ## can not evaluate init:stop() or erlang:halt() because the shell is restricted
  972. echo 'emqx_machine:brutal_shutdown().' | "$BINDIR/to_erl" "$PIPE_DIR"
  973. fi
  974. }
  975. ## Call nodetool to stop EMQX
  976. nodetool_shutdown() {
  977. # Wait for the node to completely stop...
  978. PID="$(relx_get_pid)"
  979. if ! relx_nodetool "stop"; then
  980. die "Graceful shutdown failed PID=[$PID]"
  981. fi
  982. WAIT_TIME="${EMQX_WAIT_FOR_STOP:-120}"
  983. if ! wait_for "$WAIT_TIME" 'is_down' "$PID"; then
  984. msg="dangling after ${WAIT_TIME} seconds"
  985. # also log to syslog
  986. logger -t "${REL_NAME}[${PID}]" "STOP: $msg"
  987. # log to user console
  988. set +x
  989. logerr "Stop failed, $msg"
  990. echo "ERROR: $PID is still around"
  991. ps -p "$PID"
  992. exit 1
  993. fi
  994. echo "ok"
  995. logger -t "${REL_NAME}[${PID}]" "STOP: OK"
  996. }
  997. ## make sure the CWD of emqx is BASE_RUNNER_ROOT_DIR, so relative paths like "etc/"
  998. ## and "data/" still work.
  999. cd "$BASE_RUNNER_ROOT_DIR"
  1000. case "${COMMAND}" in
  1001. start)
  1002. maybe_warn_ulimit
  1003. maybe_warn_default_cookie
  1004. # this flag passes down to console mode
  1005. # so we know it's intended to be run in daemon mode
  1006. export _EMQX_START_DAEMON_MODE=1
  1007. case "$COMMAND" in
  1008. start)
  1009. shift
  1010. START_OPTION="console"
  1011. HEART_OPTION="start"
  1012. ;;
  1013. esac
  1014. RUN_PARAM="$*"
  1015. # Set arguments for the heart command
  1016. set -- "$RUNNER_SCRIPT" "$HEART_OPTION"
  1017. [ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM"
  1018. # Export the HEART_COMMAND
  1019. HEART_COMMAND="$RUNNER_SCRIPT $COMMAND"
  1020. export HEART_COMMAND
  1021. ## See: http://erlang.org/doc/man/run_erl.html
  1022. # Export the RUN_ERL_LOG_GENERATIONS
  1023. export RUN_ERL_LOG_GENERATIONS=${RUN_ERL_LOG_GENERATIONS:-"5"}
  1024. # Export the RUN_ERL_LOG_MAXSIZE
  1025. export RUN_ERL_LOG_MAXSIZE=${RUN_ERL_LOG_MAXSIZE:-"10485760"}
  1026. mkdir -p "$PIPE_DIR"
  1027. "$BINDIR/run_erl" -daemon "$PIPE_DIR" "$EMQX_LOG_DIR" \
  1028. "$(relx_start_command)"
  1029. WAIT_TIME=${EMQX_WAIT_FOR_START:-120}
  1030. if wait_until_return_val "true" "$WAIT_TIME" 'relx_nodetool' \
  1031. 'eval' 'emqx:is_running()'; then
  1032. echo "$EMQX_DESCRIPTION $REL_VSN is started successfully!"
  1033. exit 0
  1034. else
  1035. logerr "${EMQX_DESCRIPTION} ${REL_VSN} using node name '${NAME}' failed ${WAIT_TIME} probes."
  1036. diagnose_boot_failure_and_die
  1037. fi
  1038. ;;
  1039. stop)
  1040. if ! nodetool_shutdown; then
  1041. pipe_shutdown
  1042. fi
  1043. ;;
  1044. pid)
  1045. ## Get the VM's pid
  1046. if ! relx_get_pid; then
  1047. exit 1
  1048. fi
  1049. ;;
  1050. ping)
  1051. assert_node_alive
  1052. echo pong
  1053. ;;
  1054. escript)
  1055. ## Run an escript under the node's environment
  1056. if ! relx_escript "$@"; then
  1057. exit 1
  1058. fi
  1059. ;;
  1060. attach)
  1061. exec "$BINDIR/to_erl" "$PIPE_DIR"
  1062. ;;
  1063. remote_console)
  1064. assert_node_alive
  1065. shift
  1066. remsh
  1067. ;;
  1068. upgrade|downgrade|install|unpack|uninstall)
  1069. if [ -z "${2:-}" ]; then
  1070. echo "Missing version argument"
  1071. echo "Usage: $REL_NAME $COMMAND {version}"
  1072. exit 1
  1073. fi
  1074. shift
  1075. assert_node_alive
  1076. curr_vsn="$(current_script_version)"
  1077. target_vsn="$1"
  1078. newest_vsn="$(max_version_of "$target_vsn" "$curr_vsn")"
  1079. ensure_newest_script_is_extracted "$newest_vsn"
  1080. # if we are not the newest script, run the same command from it
  1081. if ! am_i_the_newest_script "$newest_vsn"; then
  1082. script_path="$(versioned_script_path emqx "$newest_vsn")"
  1083. exec "$script_path" "$COMMAND" "$@"
  1084. fi
  1085. upgrade_script_path="$(versioned_script_path install_upgrade.escript "$newest_vsn")"
  1086. echo "using ${upgrade_script_path} to run ${COMMAND} $*"
  1087. ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
  1088. exec "$BINDIR/escript" "$upgrade_script_path" \
  1089. "$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
  1090. ;;
  1091. versions)
  1092. assert_node_alive
  1093. shift
  1094. ERL_FLAGS="${ERL_FLAGS:-} $EPMD_ARGS" \
  1095. exec "$BINDIR/escript" "$RUNNER_ROOT_DIR/bin/install_upgrade.escript" \
  1096. "versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
  1097. ;;
  1098. console|console_clean|foreground)
  1099. # .boot file typically just $REL_NAME (ie, the app name)
  1100. # however, for debugging, sometimes start_clean.boot is useful.
  1101. # For e.g. 'setup', one may even want to name another boot script.
  1102. case "$COMMAND" in
  1103. console|foreground)
  1104. if [ -f "$REL_DIR/$REL_NAME.boot" ]; then
  1105. BOOTFILE="$REL_DIR/$REL_NAME"
  1106. else
  1107. BOOTFILE="$REL_DIR/start"
  1108. fi
  1109. ;;
  1110. console_clean)
  1111. BOOTFILE="$REL_DIR/start_clean"
  1112. ;;
  1113. esac
  1114. case "$COMMAND" in
  1115. foreground)
  1116. FOREGROUNDOPTIONS="-enable-feature maybe_expr -noinput -noshell +Bd"
  1117. ;;
  1118. *)
  1119. FOREGROUNDOPTIONS='-enable-feature maybe_expr'
  1120. ;;
  1121. esac
  1122. # set before generate_config
  1123. if [ "${_EMQX_START_DAEMON_MODE:-}" = 1 ]; then
  1124. tr_log_to_env
  1125. else
  1126. maybe_log_to_console
  1127. maybe_warn_ulimit
  1128. maybe_warn_default_cookie
  1129. fi
  1130. #generate app.config and vm.args
  1131. generate_config "$NAME_TYPE" "$NAME"
  1132. check_license
  1133. # Setup beam-required vars
  1134. EMU="beam"
  1135. PROGNAME="${0}"
  1136. export EMU
  1137. export PROGNAME
  1138. # Store passed arguments since they will be erased by `set`
  1139. ARGS="$*"
  1140. # shellcheck disable=SC2086
  1141. # Build an array of arguments to pass to exec later on
  1142. # Build it here because this command will be used for logging.
  1143. if [ "$IS_ELIXIR" = no ] || [ "${EMQX_CONSOLE_FLAVOR:-}" = 'erl' ] ; then
  1144. if [[ "$DEBUG" == 2 ]]; then
  1145. INIT_DEBUG_ARG="-init_debug"
  1146. else
  1147. INIT_DEBUG_ARG=""
  1148. fi
  1149. # pass down RELEASE_LIB so we can switch to IS_ELIXIR=no
  1150. # to boot an Erlang node from the elixir release
  1151. set -- "$BINDIR/erlexec" \
  1152. $FOREGROUNDOPTIONS \
  1153. -boot "$BOOTFILE" \
  1154. -boot_var RELEASE_LIB "$ERTS_LIB_DIR" \
  1155. -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
  1156. -mode "$CODE_LOADING_MODE" \
  1157. -config "$CONF_FILE" \
  1158. -args_file "$ARGS_FILE" \
  1159. $INIT_DEBUG_ARG \
  1160. $EPMD_ARGS
  1161. else
  1162. if [[ "$DEBUG" == 2 ]]; then
  1163. INIT_DEBUG_ARG="--erl -init_debug"
  1164. else
  1165. INIT_DEBUG_ARG=""
  1166. fi
  1167. set -- "$REL_DIR/iex" \
  1168. --boot "$BOOTFILE" \
  1169. --boot-var RELEASE_LIB "${ERTS_LIB_DIR}" \
  1170. --erl-config "${CONF_FILE}" \
  1171. --vm-args "${ARGS_FILE}" \
  1172. --erl "$FOREGROUNDOPTIONS" \
  1173. --erl "-mode $CODE_LOADING_MODE" \
  1174. --erl "$EPMD_ARGS" \
  1175. $INIT_DEBUG_ARG \
  1176. --werl
  1177. fi
  1178. # Log the startup
  1179. logger -t "${REL_NAME}[$$]" "EXEC: $* -- ${1+$ARGS} -ekka_proto_dist ${EKKA_PROTO_DIST_MOD} -emqx_data_dir ${DATA_DIR}"
  1180. # Start the VM
  1181. # add ekka_proto_dist emqx_data_dir to boot command so it is visible from 'ps -ef'
  1182. # NTOE: order matters! emqx_data_dir has to be positioned at the end of the line to simplify the
  1183. # line parsing when file path contains spaces
  1184. exec "$@" -- ${1+$ARGS} -ekka_proto_dist "${EKKA_PROTO_DIST_MOD}" -emqx_data_dir "${DATA_DIR}"
  1185. ;;
  1186. ctl)
  1187. assert_node_alive
  1188. shift
  1189. relx_nodetool rpc_infinity emqx_ctl run_command "$@"
  1190. ;;
  1191. rpc)
  1192. assert_node_alive
  1193. shift
  1194. relx_nodetool rpc "$@"
  1195. ;;
  1196. rpcterms)
  1197. assert_node_alive
  1198. shift
  1199. relx_nodetool rpcterms "$@"
  1200. ;;
  1201. eval)
  1202. assert_node_alive
  1203. shift
  1204. relx_nodetool "eval" "$@"
  1205. ;;
  1206. eval-ex)
  1207. assert_node_alive
  1208. shift
  1209. if [ "$IS_ELIXIR" = "yes" ]
  1210. then
  1211. "$REL_DIR/elixir" \
  1212. --hidden \
  1213. --name "rand-$(gen_node_id)-$NAME" \
  1214. --cookie "$COOKIE" \
  1215. --boot "$REL_DIR/start_clean" \
  1216. --boot-var RELEASE_LIB "$ERTS_LIB_DIR" \
  1217. --vm-args "$REL_DIR/remote.vm.args" \
  1218. --erl "-start_epmd false" \
  1219. --erl "-epmd_module ekka_epmd" \
  1220. --erl "+P 65536" \
  1221. --erl "+Q 65536" \
  1222. --erl "+S 2" \
  1223. --rpc-eval "$NAME" "$@"
  1224. else
  1225. echo "EMQX node is not an Elixir node"
  1226. usage "$COMMAND"
  1227. exit 1
  1228. fi
  1229. ;;
  1230. check_config)
  1231. check_config
  1232. ;;
  1233. *)
  1234. usage "$COMMAND"
  1235. exit 1
  1236. ;;
  1237. esac
  1238. exit 0