emqx 45 KB

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