emqx 33 KB

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