emqx 31 KB

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