dev 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. #!/usr/bin/env bash
  2. set -euo pipefail
  3. if [ -n "${DEBUG:-}" ]; then
  4. set -x
  5. fi
  6. UNAME="$(uname -s)"
  7. PROJ_ROOT="$(git rev-parse --show-toplevel)"
  8. cd "$PROJ_ROOT"
  9. logerr() {
  10. if [ "${TERM:-dumb}" = dumb ]; then
  11. echo -e "ERROR: $*" 1>&2
  12. else
  13. echo -e "$(tput setaf 1)ERROR: $*$(tput sgr0)" 1>&2
  14. fi
  15. }
  16. usage() {
  17. cat <<EOF
  18. Run EMQX without building a release (which takes longer time).
  19. Node state is stored in '_build/dev-run/$PROFILE'.
  20. The node is started in interactive mode without a boot file.
  21. USAGE: $0 [COMMAND] [OPTION[]
  22. COMMANDS:
  23. help: Print this usage info.
  24. run: Default command.
  25. remsh: Attach to running node's remote console.
  26. Target node name is to be specified with -n|--name,
  27. otherwise defaults to 'emqx@127.0.0.1'.
  28. ctl: Equivalent to 'emqx ctl'.
  29. ctl command arguments should be passed after '--'
  30. e.g. $0 ctl -- help
  31. eval: Evaluate an Erlang expression
  32. OPTIONS:
  33. -h|--help: Print this usage info.
  34. -p|--profile: emqx | emqx-enterprise, defaults to 'PROFILE' env.
  35. -c|--compile: Force recompile, otherwise starts with the already built libs
  36. in '_build/\$PROFILE/lib/'.
  37. -e|--ekka-epmd: Force to use ekka_epmd.
  38. -n|--name: Node name, defaults to \$EMQX_NODE__NAME or the \$EMQX_NODE_NAME env.
  39. -s|--shortnames: Use shortnames for erlang node name (ie. use -sname parameter).
  40. ENVIRONMENT VARIABLES:
  41. PROFILE: Overridden by '-p|--profile' option, defaults to 'emqx'.
  42. EMQX_NODE_NAME: Overridden by '-n|--name' or '-r|--remsh' option.
  43. The node name of the EMQX node. Default to emqx@127.0.0.1'.
  44. EMQX_NODE_COOKIE: Erlang cookie, defaults to ~/.erlang.cookie
  45. EOF
  46. }
  47. if [ -n "${DEBUG:-}" ]; then
  48. set -x
  49. fi
  50. export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
  51. case "${EMQX_DEFAULT_LOG_HANDLER:-console}" in
  52. console|default)
  53. export EMQX_LOG__FILE__DEFAULT__ENABLE='false'
  54. export EMQX_LOG__CONSOLE__ENABLE='true'
  55. ;;
  56. file)
  57. export EMQX_LOG__FILE__DEFAULT__ENABLE='true'
  58. export EMQX_LOG__CONSOLE__ENABLE='false'
  59. ;;
  60. both)
  61. export EMQX_LOG__CONSOLE__ENABLE='true'
  62. export EMQX_LOG__FILE__ENABLE='true'
  63. ;;
  64. *)
  65. ;;
  66. esac
  67. SYSTEM="$(./scripts/get-distro.sh)"
  68. if [ -n "${EMQX_NODE_NAME:-}" ]; then
  69. export EMQX_NODE__NAME="${EMQX_NODE_NAME}"
  70. fi
  71. EMQX_NODE_NAME="${EMQX_NODE__NAME:-emqx@127.0.0.1}"
  72. PROFILE="${PROFILE:-emqx}"
  73. FORCE_COMPILE=0
  74. # Do not start using ekka epmd by default, so your IDE can connect to it
  75. EKKA_EPMD=0
  76. SHORTNAMES=0
  77. COMMAND='run'
  78. case "${1:-novalue}" in
  79. novalue)
  80. ;;
  81. run)
  82. shift
  83. ;;
  84. remsh)
  85. COMMAND='remsh'
  86. shift
  87. ;;
  88. ctl)
  89. COMMAND='ctl'
  90. shift
  91. ;;
  92. eval)
  93. COMMAND='eval'
  94. shift
  95. ;;
  96. help)
  97. usage
  98. exit 0
  99. ;;
  100. -*)
  101. ;;
  102. esac
  103. while [ "$#" -gt 0 ]; do
  104. case $1 in
  105. -h|--help)
  106. usage
  107. exit 0
  108. ;;
  109. -n|--name)
  110. EMQX_NODE_NAME="$2"
  111. shift 1
  112. ;;
  113. -p|--profile)
  114. PROFILE="${2}"
  115. shift 1;
  116. ;;
  117. -c|--compile)
  118. FORCE_COMPILE=1
  119. ;;
  120. -e|--ekka-epmd)
  121. EKKA_EPMD=1
  122. ;;
  123. -s|--shortnames)
  124. SHORTNAMES=1
  125. ;;
  126. --)
  127. shift
  128. PASSTHROUGH_ARGS=("$@")
  129. break
  130. ;;
  131. *)
  132. logerr "Unknown argument $1"
  133. usage
  134. exit 1
  135. ;;
  136. esac
  137. shift 1;
  138. done
  139. case "${PROFILE}" in
  140. ce|emqx)
  141. PROFILE='emqx'
  142. ;;
  143. ee|emqx-enterprise)
  144. PROFILE='emqx-enterprise'
  145. ;;
  146. pe|emqx-platform)
  147. PROFILE='emqx-platform'
  148. ;;
  149. ce-ex|emqx-elixir)
  150. export IS_ELIXIR=yes
  151. PROFILE='emqx'
  152. ;;
  153. ee-ex|emqx-enterprise-elixir)
  154. export IS_ELIXIR=yes
  155. PROFILE='emqx-enterprise'
  156. ;;
  157. *)
  158. echo "Unknown profile $PROFILE"
  159. exit 1
  160. ;;
  161. esac
  162. export PROFILE
  163. case "${PROFILE}" in
  164. emqx|emqx-elixir)
  165. export SCHEMA_MOD='emqx_conf_schema'
  166. ;;
  167. emqx-enterprise|emqx-enterprise-elixir|emqx-platform)
  168. export SCHEMA_MOD='emqx_enterprise_schema'
  169. ;;
  170. esac
  171. BASE_DIR="_build/dev-run/$PROFILE"
  172. export EMQX_ETC_DIR="$BASE_DIR/etc"
  173. export EMQX_DATA_DIR="$BASE_DIR/data"
  174. export EMQX_LOG_DIR="$BASE_DIR/log"
  175. export EMQX_PLUGINS__INSTALL_DIR="${EMQX_PLUGINS__INSTALL_DIR:-$BASE_DIR/plugins}"
  176. CONFIGS_DIR="$EMQX_DATA_DIR/configs"
  177. # Use your cookie so your IDE can connect to it.
  178. COOKIE="${EMQX_NODE__COOKIE:-${EMQX_NODE_COOKIE:-$(cat ~/.erlang.cookie 2>/dev/null || echo 'emqxsecretcookie')}}"
  179. mkdir -p "$EMQX_ETC_DIR" "$EMQX_DATA_DIR/patches" "$EMQX_DATA_DIR/plugins" "$EMQX_DATA_DIR/certs" "$EMQX_LOG_DIR" "$CONFIGS_DIR"
  180. if [ $EKKA_EPMD -eq 1 ]; then
  181. EPMD_ARGS='-start_epmd false -epmd_module ekka_epmd'
  182. else
  183. EPMD_ARGS=''
  184. fi
  185. if [ $SHORTNAMES -eq 1 ]; then
  186. ERL_NAME_ARG='-sname'
  187. else
  188. ERL_NAME_ARG='-name'
  189. fi
  190. ## build compile the profile is it's not compiled yet
  191. prepare_erl_libs() {
  192. local profile="$1"
  193. local libs_dir="_build/${profile}/lib"
  194. local erl_libs="${ERL_LIBS:-}"
  195. local sep
  196. if [ "${SYSTEM}" = 'windows' ]; then
  197. sep=';'
  198. else
  199. sep=':'
  200. fi
  201. if [ $FORCE_COMPILE -eq 1 ] || [ ! -d "$libs_dir" ]; then
  202. make "compile-${PROFILE}"
  203. else
  204. echo "Running from code in $libs_dir"
  205. fi
  206. for app in "${libs_dir}"/*; do
  207. if [ -n "$erl_libs" ]; then
  208. erl_libs="${erl_libs}${sep}${app}"
  209. else
  210. erl_libs="${app}"
  211. fi
  212. done
  213. if [ "${IS_ELIXIR:-}" = "yes" ]; then
  214. local elixir_libs_root_dir
  215. elixir_libs_root_dir=$(realpath "$(elixir -e ':code.lib_dir(:elixir) |> IO.puts()')"/..)
  216. for app in "${elixir_libs_root_dir}"/*; do
  217. if [ -n "$erl_libs" ]; then
  218. erl_libs="${erl_libs}${sep}${app}"
  219. else
  220. erl_libs="${app}"
  221. fi
  222. done
  223. fi
  224. if [ -e "_build/${profile}/checkouts" ]; then
  225. for app in "_build/${profile}/checkouts"/*; do
  226. erl_libs="${erl_libs}${sep}${app}"
  227. done
  228. fi
  229. export ERL_LIBS="$erl_libs"
  230. }
  231. ## poorman's mustache templating
  232. mustache() {
  233. local name="$1"
  234. local value="$2"
  235. local file="$3"
  236. if [[ "$UNAME" == "Darwin" ]]; then
  237. sed -i '' "s|{{[[:space:]]*${name}[[:space:]]*}}|${value}|g" "$file"
  238. else
  239. sed -i "s|{{\s*${name}\s*}}|${value}|g" "$file"
  240. fi
  241. }
  242. ## render the merged boot conf file.
  243. ## the merge action is done before the profile is compiled
  244. render_hocon_conf() {
  245. input="apps/emqx_conf/etc/emqx.conf.all"
  246. output="$EMQX_ETC_DIR/emqx.conf"
  247. cp "$input" "$output"
  248. mustache emqx_default_erlang_cookie "$COOKIE" "$output"
  249. mustache platform_data_dir "${EMQX_DATA_DIR}" "$output"
  250. mustache platform_log_dir "${EMQX_LOG_DIR}" "$output"
  251. mustache platform_etc_dir "${EMQX_ETC_DIR}" "$output"
  252. }
  253. ## Make comma separated quoted strings
  254. make_erlang_args() {
  255. local in=("$@")
  256. local args=''
  257. for arg in "${in[@]}"; do
  258. if [ -z "$args" ]; then
  259. args="\"$arg\""
  260. else
  261. args="$args, \"$arg\""
  262. fi
  263. done
  264. echo "$args"
  265. }
  266. call_hocon() {
  267. local args erl_code
  268. args="$(make_erlang_args "$@")"
  269. erl_code="
  270. {ok, _} = application:ensure_all_started(hocon), \
  271. try
  272. mnesia_hook:module_info()
  273. catch _:_->
  274. io:format(standard_error, \"Force setting DB backend to 'mnesia', and 'role' to 'core'~n\", []),
  275. os:putenv(\"EMQX_NODE__DB_BACKEND\", \"mnesia\"),
  276. os:putenv(\"EMQX_NODE__DB_ROLE\", \"core\")
  277. end,
  278. {Time, ok} = timer:tc(fun() -> ok = hocon_cli:main([$args]) end),
  279. io:format(user, \"Took ~pms to generate config~n\", [Time div 1000]),
  280. init:stop().
  281. "
  282. erl -noshell -eval "$erl_code"
  283. }
  284. # Function to generate app.config and vm.args
  285. # sets two environment variables CONF_FILE and ARGS_FILE
  286. generate_app_conf() {
  287. ## timestamp for each generation
  288. local NOW_TIME
  289. NOW_TIME="$(date +'%Y.%m.%d.%H.%M.%S')"
  290. ## This command populates two files: app.<time>.config and vm.<time>.args
  291. ## It takes input sources and overlays values in below order:
  292. ## - $DATA_DIR/cluster.hocon (if exists)
  293. ## - etc/emqx.conf
  294. ## - environment variables starts with EMQX_ e.g. EMQX_NODE__ROLE
  295. ##
  296. ## NOTE: it's a known issue that cluster.hocon may change right after the node boots up
  297. ## because it has to sync cluster.hocon from other nodes.
  298. call_hocon -v -t "$NOW_TIME" -s "$SCHEMA_MOD" \
  299. -c "$EMQX_DATA_DIR"/configs/cluster.hocon \
  300. -c "$EMQX_ETC_DIR"/emqx.conf \
  301. -d "$EMQX_DATA_DIR"/configs generate
  302. ## filenames are per-hocon convention
  303. CONF_FILE="$CONFIGS_DIR/app.$NOW_TIME.config"
  304. ARGS_FILE="$CONFIGS_DIR/vm.$NOW_TIME.args"
  305. }
  306. # apps/emqx/etc/vm.args.cloud
  307. append_args_file() {
  308. ## ensure a new line at the end
  309. echo '' >> "$ARGS_FILE"
  310. cat <<EOF >> "$ARGS_FILE"
  311. $ERL_NAME_ARG $EMQX_NODE_NAME
  312. -mnesia dir '"$EMQX_DATA_DIR/mnesia/$EMQX_NODE_NAME"'
  313. -stdlib restricted_shell emqx_restricted_shell
  314. +spp true
  315. +A 4
  316. +IOt 4
  317. +SDio 8
  318. -shutdown_time 30000
  319. -pa '$EMQX_DATA_DIR/patches'
  320. -cache_boot_paths false
  321. -mnesia dump_log_write_threshold 5000
  322. -mnesia dump_log_time_threshold 60000
  323. -os_mon start_disksup false
  324. EOF
  325. }
  326. # copy cert files and acl.conf to etc
  327. copy_other_conf_files() {
  328. cp -r apps/emqx/etc/certs "$EMQX_ETC_DIR"/
  329. cp apps/emqx_auth/etc/acl.conf "$EMQX_ETC_DIR"/
  330. }
  331. is_current_profile_app() {
  332. local app="$1"
  333. case "$app" in
  334. *emqx_telemetry*)
  335. if [ "$PROFILE" = 'emqx-enterprise' ]; then
  336. return 1
  337. else
  338. return 0
  339. fi
  340. ;;
  341. *)
  342. if [ "$PROFILE" = 'emqx' ]; then
  343. if [ -f "$app"/BSL.txt ]; then
  344. return 1
  345. else
  346. return 0
  347. fi
  348. else
  349. return 0
  350. fi
  351. ;;
  352. esac
  353. }
  354. ## apps to load
  355. apps_to_load() {
  356. local apps csl
  357. apps="$(./scripts/find-apps.sh | xargs)"
  358. csl=""
  359. for app in $apps; do
  360. if ! is_current_profile_app "$app"; then
  361. continue
  362. fi
  363. name="$(basename "$app")"
  364. if [ -z "$csl" ]; then
  365. csl="$name"
  366. else
  367. csl="$csl,$name"
  368. fi
  369. done
  370. echo "$csl"
  371. }
  372. boot() {
  373. ## Make erl command aware where to load all the beams
  374. ## this should be done before every erl command
  375. prepare_erl_libs "$PROFILE"
  376. ## make sure copy acl.conf and certs to etc before render_hocon_conf
  377. ## hocon will check rules inside acl.conf.
  378. copy_other_conf_files
  379. render_hocon_conf
  380. generate_app_conf
  381. append_args_file
  382. APPS="$(apps_to_load)"
  383. if [ "${IS_ELIXIR:-}" = "yes" ]; then
  384. BOOT_SEQUENCE='
  385. apps = System.get_env("APPS") |> String.split(",") |> Enum.map(&String.to_atom/1)
  386. Enum.each(apps, &Application.load/1)
  387. IO.inspect(apps, label: :loaded, limit: :infinity)
  388. {:ok, _} = Application.ensure_all_started(:emqx_machine)
  389. '
  390. if [ -n "${EPMD_ARGS:-}" ]; then
  391. EPMD_ARGS_ELIXIR="$EPMD_ARGS"
  392. else
  393. EPMD_ARGS_ELIXIR="-no_op true"
  394. fi
  395. local OTP_VSN USER_MOD_ARG
  396. OTP_VSN=$(./scripts/get-otp-vsn.sh)
  397. case "$OTP_VSN" in
  398. 25*)
  399. USER_MOD_ARG='-user Elixir.IEx.CLI'
  400. ;;
  401. *)
  402. USER_MOD_ARG='-user elixir'
  403. ;;
  404. esac
  405. # shellcheck disable=SC2086
  406. env APPS="$APPS" iex \
  407. -$ERL_NAME_ARG "$EMQX_NODE_NAME" \
  408. --erl "$EPMD_ARGS_ELIXIR" \
  409. --erl "$USER_MOD_ARG" \
  410. --erl '-proto_dist ekka' \
  411. --vm-args "$ARGS_FILE" \
  412. --erl-config "$CONF_FILE" \
  413. -e "$BOOT_SEQUENCE"
  414. else
  415. BOOT_SEQUENCE="
  416. Apps=[${APPS}],
  417. ok=lists:foreach(fun application:load/1, Apps),
  418. io:format(user, \"~nLoaded: ~p~n\", [Apps]),
  419. {ok, _} = application:ensure_all_started(emqx_machine).
  420. "
  421. # shellcheck disable=SC2086
  422. erl $ERL_NAME_ARG "$EMQX_NODE_NAME" \
  423. $EPMD_ARGS \
  424. -proto_dist ekka \
  425. -args_file "$ARGS_FILE" \
  426. -config "$CONF_FILE" \
  427. -s emqx_restricted_shell set_prompt_func \
  428. -eval "$BOOT_SEQUENCE"
  429. fi
  430. }
  431. # Generate a random id
  432. gen_tmp_node_name() {
  433. local rnd
  434. rnd="$(od -t u -N 4 /dev/urandom | head -n1 | awk '{print $2 % 1000}')"
  435. if [ $SHORTNAMES -eq 1 ]; then
  436. echo "remsh${rnd}"
  437. else
  438. echo "remsh${rnd}-${EMQX_NODE_NAME}"
  439. fi
  440. }
  441. remsh() {
  442. local tmpnode
  443. tmpnode="$(gen_tmp_node_name)"
  444. # shellcheck disable=SC2086
  445. erl $ERL_NAME_ARG "$tmpnode" \
  446. -hidden \
  447. -setcookie "$COOKIE" \
  448. -remsh "$EMQX_NODE_NAME" \
  449. $EPMD_ARGS
  450. }
  451. # evaluate erlang expression in remsh node
  452. eval_remsh_erl() {
  453. local tmpnode erl_code
  454. tmpnode="$(gen_tmp_node_name)"
  455. erl_code="$1"
  456. # shellcheck disable=SC2086 # need to expand EMQD_ARGS
  457. erl $ERL_NAME_ARG "$tmpnode" -setcookie "$COOKIE" -hidden -noshell $EPMD_ARGS -eval "$erl_code" 2>&1
  458. }
  459. ctl() {
  460. if [ -z "${PASSTHROUGH_ARGS:-}" ]; then
  461. logerr "Need at least one argument for ctl command"
  462. logerr "e.g. $0 ctl -- help"
  463. exit 1
  464. fi
  465. local args rpc_code output result
  466. args="$(make_erlang_args "${PASSTHROUGH_ARGS[@]}")"
  467. rpc_code="
  468. case rpc:call('$EMQX_NODE_NAME', emqx_ctl, run_command, [[$args]]) of
  469. ok ->
  470. init:stop(0);
  471. true ->
  472. init:stop(0);
  473. Error ->
  474. io:format(\"~p~n\", [Error]),
  475. init:stop(1)
  476. end"
  477. set +e
  478. output="$(eval_remsh_erl "$rpc_code")"
  479. result=$?
  480. if [ $result -eq 0 ]; then
  481. echo -e "$output"
  482. else
  483. logerr "$output"
  484. fi
  485. exit $result
  486. }
  487. case "$COMMAND" in
  488. run)
  489. boot
  490. ;;
  491. remsh)
  492. remsh
  493. ;;
  494. ctl)
  495. ctl
  496. ;;
  497. eval)
  498. PASSTHROUGH_ARGS=('eval_erl' "${PASSTHROUGH_ARGS[@]}")
  499. ctl
  500. ;;
  501. esac