emqx 43 KB

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