dev 12 KB

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