dev 12 KB

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