emqx 34 KB

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