emqx 35 KB

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