| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547 |
- #!/usr/bin/env bash
- set -euo pipefail
- if [ -n "${DEBUG:-}" ]; then
- set -x
- fi
- UNAME="$(uname -s)"
- PROJ_ROOT="$(git rev-parse --show-toplevel)"
- cd "$PROJ_ROOT"
- logerr() {
- if [ "${TERM:-dumb}" = dumb ]; then
- echo -e "ERROR: $*" 1>&2
- else
- echo -e "$(tput setaf 1)ERROR: $*$(tput sgr0)" 1>&2
- fi
- }
- usage() {
- cat <<EOF
- Run EMQX without building a release (which takes longer time).
- Node state is stored in '_build/dev-run/$PROFILE'.
- The node is started in interactive mode without a boot file.
- USAGE: $0 [COMMAND] [OPTION[]
- COMMANDS:
- help: Print this usage info.
- run: Default command.
- remsh: Attach to running node's remote console.
- Target node name is to be specified with -n|--name,
- otherwise defaults to 'emqx@127.0.0.1'.
- ctl: Equivalent to 'emqx ctl'.
- ctl command arguments should be passed after '--'
- e.g. $0 ctl -- help
- eval: Evaluate an Erlang expression
- OPTIONS:
- -h|--help: Print this usage info.
- -p|--profile: emqx | emqx-enterprise, defaults to 'PROFILE' env.
- -c|--compile: Force recompile, otherwise starts with the already built libs
- in '_build/\$PROFILE/lib/'.
- -e|--ekka-epmd: Force to use ekka_epmd.
- -n|--name: Node name, defaults to \$EMQX_NODE__NAME or the \$EMQX_NODE_NAME env.
- -s|--shortnames: Use shortnames for erlang node name (ie. use -sname parameter).
- ENVIRONMENT VARIABLES:
- PROFILE: Overridden by '-p|--profile' option, defaults to 'emqx'.
- EMQX_NODE_NAME: Overridden by '-n|--name' or '-r|--remsh' option.
- The node name of the EMQX node. Default to emqx@127.0.0.1'.
- EMQX_NODE_COOKIE: Erlang cookie, defaults to ~/.erlang.cookie
- EOF
- }
- if [ -n "${DEBUG:-}" ]; then
- set -x
- fi
- export HOCON_ENV_OVERRIDE_PREFIX='EMQX_'
- case "${EMQX_DEFAULT_LOG_HANDLER:-console}" in
- console|default)
- export EMQX_LOG__FILE__DEFAULT__ENABLE='false'
- export EMQX_LOG__CONSOLE__ENABLE='true'
- ;;
- file)
- export EMQX_LOG__FILE__DEFAULT__ENABLE='true'
- export EMQX_LOG__CONSOLE__ENABLE='false'
- ;;
- both)
- export EMQX_LOG__CONSOLE__ENABLE='true'
- export EMQX_LOG__FILE__ENABLE='true'
- ;;
- *)
- ;;
- esac
- SYSTEM="$(./scripts/get-distro.sh)"
- if [ -n "${EMQX_NODE_NAME:-}" ]; then
- export EMQX_NODE__NAME="${EMQX_NODE_NAME}"
- fi
- EMQX_NODE_NAME="${EMQX_NODE__NAME:-emqx@127.0.0.1}"
- PROFILE="${PROFILE:-emqx}"
- FORCE_COMPILE=0
- # Do not start using ekka epmd by default, so your IDE can connect to it
- EKKA_EPMD=0
- SHORTNAMES=0
- COMMAND='run'
- case "${1:-novalue}" in
- novalue)
- ;;
- run)
- shift
- ;;
- remsh)
- COMMAND='remsh'
- shift
- ;;
- ctl)
- COMMAND='ctl'
- shift
- ;;
- eval)
- COMMAND='eval'
- shift
- ;;
- help)
- usage
- exit 0
- ;;
- -*)
- ;;
- esac
- while [ "$#" -gt 0 ]; do
- case $1 in
- -h|--help)
- usage
- exit 0
- ;;
- -n|--name)
- EMQX_NODE_NAME="$2"
- shift 1
- ;;
- -p|--profile)
- PROFILE="${2}"
- shift 1;
- ;;
- -c|--compile)
- FORCE_COMPILE=1
- ;;
- -e|--ekka-epmd)
- EKKA_EPMD=1
- ;;
- -s|--shortnames)
- SHORTNAMES=1
- ;;
- --)
- shift
- PASSTHROUGH_ARGS=("$@")
- break
- ;;
- *)
- logerr "Unknown argument $1"
- usage
- exit 1
- ;;
- esac
- shift 1;
- done
- case "${PROFILE}" in
- ce|emqx)
- PROFILE='emqx'
- ;;
- ee|emqx-enterprise)
- PROFILE='emqx-enterprise'
- ;;
- pe|emqx-platform)
- PROFILE='emqx-platform'
- ;;
- ce-ex|emqx-elixir)
- export IS_ELIXIR=yes
- PROFILE='emqx'
- ;;
- ee-ex|emqx-enterprise-elixir)
- export IS_ELIXIR=yes
- PROFILE='emqx-enterprise'
- ;;
- *)
- echo "Unknown profile $PROFILE"
- exit 1
- ;;
- esac
- export PROFILE
- case "${PROFILE}" in
- emqx|emqx-elixir)
- export SCHEMA_MOD='emqx_conf_schema'
- ;;
- emqx-enterprise|emqx-enterprise-elixir|emqx-platform)
- export SCHEMA_MOD='emqx_enterprise_schema'
- ;;
- esac
- BASE_DIR="_build/dev-run/$PROFILE"
- export EMQX_ETC_DIR="$BASE_DIR/etc"
- export EMQX_DATA_DIR="$BASE_DIR/data"
- export EMQX_LOG_DIR="$BASE_DIR/log"
- export EMQX_PLUGINS__INSTALL_DIR="${EMQX_PLUGINS__INSTALL_DIR:-$BASE_DIR/plugins}"
- CONFIGS_DIR="$EMQX_DATA_DIR/configs"
- # Use your cookie so your IDE can connect to it.
- COOKIE="${EMQX_NODE__COOKIE:-${EMQX_NODE_COOKIE:-$(cat ~/.erlang.cookie 2>/dev/null || echo 'emqxsecretcookie')}}"
- mkdir -p "$EMQX_ETC_DIR" "$EMQX_DATA_DIR/patches" "$EMQX_DATA_DIR/plugins" "$EMQX_DATA_DIR/certs" "$EMQX_LOG_DIR" "$CONFIGS_DIR"
- if [ $EKKA_EPMD -eq 1 ]; then
- EPMD_ARGS='-start_epmd false -epmd_module ekka_epmd'
- else
- EPMD_ARGS=''
- fi
- if [ $SHORTNAMES -eq 1 ]; then
- ERL_NAME_ARG='-sname'
- else
- ERL_NAME_ARG='-name'
- fi
- ## build compile the profile is it's not compiled yet
- prepare_erl_libs() {
- local profile="$1"
- local libs_dir="_build/${profile}/lib"
- local erl_libs="${ERL_LIBS:-}"
- local sep
- if [ "${SYSTEM}" = 'windows' ]; then
- sep=';'
- else
- sep=':'
- fi
- if [ $FORCE_COMPILE -eq 1 ] || [ ! -d "$libs_dir" ]; then
- make "compile-${PROFILE}"
- else
- echo "Running from code in $libs_dir"
- fi
- for app in "${libs_dir}"/*; do
- if [ -n "$erl_libs" ]; then
- erl_libs="${erl_libs}${sep}${app}"
- else
- erl_libs="${app}"
- fi
- done
- if [ "${IS_ELIXIR:-}" = "yes" ]; then
- local elixir_libs_root_dir
- elixir_libs_root_dir=$(realpath "$(elixir -e ':code.lib_dir(:elixir) |> IO.puts()')"/..)
- for app in "${elixir_libs_root_dir}"/*; do
- if [ -n "$erl_libs" ]; then
- erl_libs="${erl_libs}${sep}${app}"
- else
- erl_libs="${app}"
- fi
- done
- fi
- if [ -e "_build/${profile}/checkouts" ]; then
- for app in "_build/${profile}/checkouts"/*; do
- erl_libs="${erl_libs}${sep}${app}"
- done
- fi
- export ERL_LIBS="$erl_libs"
- }
- ## poorman's mustache templating
- mustache() {
- local name="$1"
- local value="$2"
- local file="$3"
- if [[ "$UNAME" == "Darwin" ]]; then
- sed -i '' "s|{{[[:space:]]*${name}[[:space:]]*}}|${value}|g" "$file"
- else
- sed -i "s|{{\s*${name}\s*}}|${value}|g" "$file"
- fi
- }
- ## render the merged boot conf file.
- ## the merge action is done before the profile is compiled
- render_hocon_conf() {
- input="apps/emqx_conf/etc/emqx.conf.all"
- output="$EMQX_ETC_DIR/emqx.conf"
- cp "$input" "$output"
- mustache emqx_default_erlang_cookie "$COOKIE" "$output"
- mustache platform_data_dir "${EMQX_DATA_DIR}" "$output"
- mustache platform_log_dir "${EMQX_LOG_DIR}" "$output"
- mustache platform_etc_dir "${EMQX_ETC_DIR}" "$output"
- }
- ## Make comma separated quoted strings
- make_erlang_args() {
- local in=("$@")
- local args=''
- for arg in "${in[@]}"; do
- if [ -z "$args" ]; then
- args="\"$arg\""
- else
- args="$args, \"$arg\""
- fi
- done
- echo "$args"
- }
- call_hocon() {
- local args erl_code
- args="$(make_erlang_args "$@")"
- erl_code="
- {ok, _} = application:ensure_all_started(hocon), \
- try
- mnesia_hook:module_info()
- catch _:_->
- io:format(standard_error, \"Force setting DB backend to 'mnesia', and 'role' to 'core'~n\", []),
- os:putenv(\"EMQX_NODE__DB_BACKEND\", \"mnesia\"),
- os:putenv(\"EMQX_NODE__DB_ROLE\", \"core\")
- end,
- {Time, ok} = timer:tc(fun() -> ok = hocon_cli:main([$args]) end),
- io:format(user, \"Took ~pms to generate config~n\", [Time div 1000]),
- init:stop().
- "
- erl -noshell -eval "$erl_code"
- }
- # Function to generate app.config and vm.args
- # sets two environment variables CONF_FILE and ARGS_FILE
- generate_app_conf() {
- ## timestamp for each generation
- local NOW_TIME
- NOW_TIME="$(date +'%Y.%m.%d.%H.%M.%S')"
- ## This command populates two files: app.<time>.config and vm.<time>.args
- ## It takes input sources and overlays values in below order:
- ## - etc/base.hocon
- ## - $DATA_DIR/cluster.hocon (if exists)
- ## - etc/emqx.conf
- ## - environment variables starts with EMQX_ e.g. EMQX_NODE__ROLE
- ##
- ## NOTE: it's a known issue that cluster.hocon may change right after the node boots up
- ## because it has to sync cluster.hocon from other nodes.
- call_hocon -v -t "$NOW_TIME" -s "$SCHEMA_MOD" \
- -c "$EMQX_ETC_DIR"/base.hocon \
- -c "$EMQX_DATA_DIR"/configs/cluster.hocon \
- -c "$EMQX_ETC_DIR"/emqx.conf \
- -d "$EMQX_DATA_DIR"/configs generate
- ## filenames are per-hocon convention
- CONF_FILE="$CONFIGS_DIR/app.$NOW_TIME.config"
- ARGS_FILE="$CONFIGS_DIR/vm.$NOW_TIME.args"
- }
- # apps/emqx/etc/vm.args.cloud
- append_args_file() {
- ## ensure a new line at the end
- echo '' >> "$ARGS_FILE"
- cat <<EOF >> "$ARGS_FILE"
- $ERL_NAME_ARG $EMQX_NODE_NAME
- -mnesia dir '"$EMQX_DATA_DIR/mnesia/$EMQX_NODE_NAME"'
- -stdlib restricted_shell emqx_restricted_shell
- +spp true
- +A 4
- +IOt 4
- +SDio 8
- -shutdown_time 30000
- -pa '$EMQX_DATA_DIR/patches'
- -cache_boot_paths false
- -mnesia dump_log_write_threshold 5000
- -mnesia dump_log_time_threshold 60000
- -os_mon start_disksup false
- EOF
- }
- # copy cert files and acl.conf to etc
- copy_other_conf_files() {
- cp -r apps/emqx/etc/certs "$EMQX_ETC_DIR"/
- cp -r apps/emqx_conf/etc/base.hocon "$EMQX_ETC_DIR"/
- cp apps/emqx_auth/etc/acl.conf "$EMQX_ETC_DIR"/
- }
- is_current_profile_app() {
- local app="$1"
- case "$app" in
- *emqx_telemetry*)
- if [ "$PROFILE" = 'emqx-enterprise' ]; then
- return 1
- else
- return 0
- fi
- ;;
- *)
- if [ "$PROFILE" = 'emqx' ]; then
- if [ -f "$app"/BSL.txt ]; then
- return 1
- else
- return 0
- fi
- else
- return 0
- fi
- ;;
- esac
- }
- ## apps to load
- apps_to_load() {
- local apps csl
- apps="$(./scripts/find-apps.sh | xargs)"
- csl=""
- for app in $apps; do
- if ! is_current_profile_app "$app"; then
- continue
- fi
- name="$(basename "$app")"
- if [ -z "$csl" ]; then
- csl="$name"
- else
- csl="$csl,$name"
- fi
- done
- echo "$csl"
- }
- boot() {
- ## Make erl command aware where to load all the beams
- ## this should be done before every erl command
- prepare_erl_libs "$PROFILE"
- ## make sure copy acl.conf and certs to etc before render_hocon_conf
- ## hocon will check rules inside acl.conf.
- copy_other_conf_files
- render_hocon_conf
- generate_app_conf
- append_args_file
- APPS="$(apps_to_load)"
- if [ "${IS_ELIXIR:-}" = "yes" ]; then
- BOOT_SEQUENCE='
- apps = System.get_env("APPS") |> String.split(",") |> Enum.map(&String.to_atom/1)
- Enum.each(apps, &Application.load/1)
- IO.inspect(apps, label: :loaded, limit: :infinity)
- {:ok, _} = Application.ensure_all_started(:emqx_machine)
- '
- if [ -n "${EPMD_ARGS:-}" ]; then
- EPMD_ARGS_ELIXIR="$EPMD_ARGS"
- else
- EPMD_ARGS_ELIXIR="-no_op true"
- fi
- local OTP_VSN USER_MOD_ARG
- OTP_VSN=$(./scripts/get-otp-vsn.sh)
- case "$OTP_VSN" in
- 25*)
- USER_MOD_ARG='-user Elixir.IEx.CLI'
- ;;
- *)
- USER_MOD_ARG='-user elixir'
- ;;
- esac
- # shellcheck disable=SC2086
- env APPS="$APPS" iex \
- -$ERL_NAME_ARG "$EMQX_NODE_NAME" \
- --erl "$EPMD_ARGS_ELIXIR" \
- --erl "$USER_MOD_ARG" \
- --erl '-proto_dist ekka' \
- --vm-args "$ARGS_FILE" \
- --erl-config "$CONF_FILE" \
- -e "$BOOT_SEQUENCE"
- else
- BOOT_SEQUENCE="
- Apps=[${APPS}],
- ok=lists:foreach(fun application:load/1, Apps),
- io:format(user, \"~nLoaded: ~p~n\", [Apps]),
- {ok, _} = application:ensure_all_started(emqx_machine).
- "
- # shellcheck disable=SC2086
- erl $ERL_NAME_ARG "$EMQX_NODE_NAME" \
- $EPMD_ARGS \
- -proto_dist ekka \
- -args_file "$ARGS_FILE" \
- -config "$CONF_FILE" \
- -s emqx_restricted_shell set_prompt_func \
- -eval "$BOOT_SEQUENCE"
- fi
- }
- # Generate a random id
- gen_tmp_node_name() {
- local rnd
- rnd="$(od -t u -N 4 /dev/urandom | head -n1 | awk '{print $2 % 1000}')"
- if [ $SHORTNAMES -eq 1 ]; then
- echo "remsh${rnd}"
- else
- echo "remsh${rnd}-${EMQX_NODE_NAME}"
- fi
- }
- remsh() {
- local tmpnode
- tmpnode="$(gen_tmp_node_name)"
- # shellcheck disable=SC2086
- erl $ERL_NAME_ARG "$tmpnode" \
- -hidden \
- -setcookie "$COOKIE" \
- -remsh "$EMQX_NODE_NAME" \
- $EPMD_ARGS
- }
- # evaluate erlang expression in remsh node
- eval_remsh_erl() {
- local tmpnode erl_code
- tmpnode="$(gen_tmp_node_name)"
- erl_code="$1"
- # shellcheck disable=SC2086 # need to expand EMQD_ARGS
- erl $ERL_NAME_ARG "$tmpnode" -setcookie "$COOKIE" -hidden -noshell $EPMD_ARGS -eval "$erl_code" 2>&1
- }
- ctl() {
- if [ -z "${PASSTHROUGH_ARGS:-}" ]; then
- logerr "Need at least one argument for ctl command"
- logerr "e.g. $0 ctl -- help"
- exit 1
- fi
- local args rpc_code output result
- args="$(make_erlang_args "${PASSTHROUGH_ARGS[@]}")"
- rpc_code="
- case rpc:call('$EMQX_NODE_NAME', emqx_ctl, run_command, [[$args]]) of
- ok ->
- init:stop(0);
- true ->
- init:stop(0);
- Error ->
- io:format(\"~p~n\", [Error]),
- init:stop(1)
- end"
- set +e
- output="$(eval_remsh_erl "$rpc_code")"
- result=$?
- if [ $result -eq 0 ]; then
- echo -e "$output"
- else
- logerr "$output"
- fi
- exit $result
- }
- case "$COMMAND" in
- run)
- boot
- ;;
- remsh)
- remsh
- ;;
- ctl)
- ctl
- ;;
- eval)
- PASSTHROUGH_ARGS=('eval_erl' "${PASSTHROUGH_ARGS[@]}")
- ctl
- ;;
- esac
|