diff --git a/.gitignore b/.gitignore index 8e6271dba..0982fe30f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ deps erl_crash.dump ebin !ebin/.placeholder -rel/emqttd -rel/emqttd* .concrete/DEV_MODE .rebar test/ebin/*.beam @@ -28,3 +26,4 @@ logs ct.coverdata .idea/ emqttd.iml +_rel/ diff --git a/bin/emqttd b/bin/emqttd index b78e68e25..e04524fd4 100755 --- a/bin/emqttd +++ b/bin/emqttd @@ -2,46 +2,21 @@ # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et -# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. -if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then - POSIX_SHELL="true" - export POSIX_SHELL - # To support 'whoami' add /usr/ucb to path - PATH=/usr/ucb:$PATH - export PATH - exec /usr/bin/ksh $0 "$@" -fi -unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well +set -e -RUNNER_SCRIPT_DIR={{runner_script_dir}} -RUNNER_SCRIPT=${0##*/} - -RUNNER_BASE_DIR={{runner_base_dir}} -RUNNER_ETC_DIR={{runner_etc_dir}} -RUNNER_LIB_DIR={{platform_lib_dir}} -RUNNER_LOG_DIR={{runner_log_dir}} -RUNNER_DATA_DIR=$RUNNER_BASE_DIR/data -RUNNER_PLUGINS_DIR=$RUNNER_BASE_DIR/plugins - -# Note the trailing slash on $PIPE_DIR/ -PIPE_DIR={{pipe_dir}} -RUNNER_USER={{runner_user}} -PLATFORM_DATA_DIR={{platform_data_dir}} -SSL_DIST_CONFIG=$PLATFORM_DATA_DIR/ssl_distribution.args_file -RIAK_VERSION="git" - -WHOAMI=$(whoami) - -# Make sure this script is running as the appropriate user -if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then - type sudo > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 - exit 1 - fi - echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2 - exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ -fi +SCRIPT=$(readlink $0 || true) +if [ -z $SCRIPT ]; then + SCRIPT=$0 +fi; +SCRIPT_DIR="$(cd `dirname "$SCRIPT"` && pwd -P)" +RELEASE_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)" +REL_NAME="emqttd" +REL_VSN="{{ rel_vsn }}" +ERTS_VSN="{{ erts_vsn }}" +CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}" +REL_DIR="$RELEASE_ROOT_DIR/releases/$REL_VSN" +ERL_OPTS="{{ erl_opts }}" +RUNNER_LOG_DIR="${RUNNER_LOG_DIR:-$RELEASE_ROOT_DIR/log}" # Warn the user if ulimit -n is less than 1024 ULIMIT_F=`ulimit -n` @@ -51,153 +26,231 @@ if [ "$ULIMIT_F" -lt 1024 ]; then echo "!!!!" fi -# Make sure CWD is set to runner base dir -cd $RUNNER_BASE_DIR +find_erts_dir() { + __erts_dir="$RELEASE_ROOT_DIR/erts-$ERTS_VSN" + if [ -d "$__erts_dir" ]; then + ERTS_DIR="$__erts_dir"; + ROOTDIR="$RELEASE_ROOT_DIR" + else + __erl="$(which erl)" + code="io:format(\"~s\", [code:root_dir()]), halt()." + __erl_root="$("$__erl" -noshell -eval "$code")" + ERTS_DIR="$__erl_root/erts-$ERTS_VSN" + ROOTDIR="$__erl_root" + fi +} + +# Get node pid +relx_get_pid() { + if output="$(relx_nodetool rpcterms os getpid)" + then + echo "$output" | sed -e 's/"//g' + return 0 + else + echo "$output" + return 1 + fi +} + +relx_get_nodename() { + id="longname$(relx_gen_id)-${NAME}" + "$BINDIR/erl" -boot start_clean -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell ${NAME_TYPE} $id +} + +# Connect to a remote node +relx_rem_sh() { + # Generate a unique id used to allow multiple remsh to the same node + # transparently + id="remsh$(relx_gen_id)-${NAME}" + + # Get the node's ticktime so that we use the same thing. + TICKTIME="$(relx_nodetool rpcterms net_kernel get_net_ticktime)" + + # Setup remote shell command to control node + exec "$BINDIR/erl" "$NAME_TYPE" "$id" -remsh "$NAME" -boot start_clean \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -setcookie "$COOKIE" -hidden -kernel net_ticktime $TICKTIME +} + +# Generate a random id +relx_gen_id() { + od -t x -N 4 /dev/urandom | head -n1 | awk '{print $2}' +} + +# Control a node +relx_nodetool() { + command="$1"; shift + + "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \ + -setcookie "$COOKIE" "$command" $@ +} + +# Run an escript in the node's environment +relx_escript() { + shift; scriptpath="$1"; shift + export RELEASE_ROOT_DIR + + "$ERTS_DIR/bin/escript" "$ROOTDIR/$scriptpath" $@ +} + +# Output a start command for the last argument of run_erl +relx_start_command() { + printf "exec \"%s\" \"%s\"" "$RELEASE_ROOT_DIR/bin/$REL_NAME" \ + "$START_OPTION" +} + +# Use $CWD/vm.args if exists, otherwise releases/VSN/vm.args +if [ -z "$VMARGS_PATH" ]; then + if [ -f "$RELEASE_ROOT_DIR/vm.args" ]; then + VMARGS_PATH="$RELEASE_ROOT_DIR/vm.args" + else + VMARGS_PATH="$REL_DIR/vm.args" + fi +fi + +orig_vmargs_path="$VMARGS_PATH.orig" +if [ $RELX_REPLACE_OS_VARS ]; then + #Make sure we don't break dev mode by keeping the symbolic link to + #the user's vm.args + if [ ! -L "$orig_vmargs_path" ]; then + #we're in copy mode, rename the vm.args file to vm.args.orig + mv "$VMARGS_PATH" "$orig_vmargs_path" + fi + + awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < "$orig_vmargs_path" > "$VMARGS_PATH" + else + #We don't need to replace env. vars, just rename the + #symlink vm.args.orig to vm.args, and keep it as a + #symlink. + if [ -L "$orig_vmargs_path" ]; then + mv "$orig_vmargs_path" "$VMARGS_PATH" + fi +fi # Make sure log directory exists -mkdir -p $RUNNER_LOG_DIR +mkdir -p "$RUNNER_LOG_DIR" -# Make sure the data directory exists -mkdir -p $PLATFORM_DATA_DIR +# Use $CWD/sys.config if exists, otherwise releases/VSN/sys.config +if [ -z "$RELX_CONFIG_PATH" ]; then + if [ -f "$RELEASE_ROOT_DIR/sys.config" ]; then + RELX_CONFIG_PATH="$RELEASE_ROOT_DIR/sys.config" + else + RELX_CONFIG_PATH="$REL_DIR/sys.config" + fi +fi -# Warn the user if they don't have write permissions on the log dir -if [ ! -w $RUNNER_LOG_DIR ]; then - echo "!!!!" - echo "!!!! WARNING: $RUNNER_LOG_DIR not writable; logs and crash dumps unavailable." - echo "!!!!" +orig_relx_config_path="$RELX_CONFIG_PATH.orig" +if [ $RELX_REPLACE_OS_VARS ]; then + #Make sure we don't break dev mode by keeping the symbolic link to + #the user's sys.config + if [ ! -L "$orig_relx_config_path" ]; then + #We're in copy mode, rename sys.config to sys.config.orig + mv "$RELX_CONFIG_PATH" "$orig_relx_config_path" + fi + + awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < "$orig_relx_config_path" > "$RELX_CONFIG_PATH" + else + #We don't need to replace env. vars, just rename the + #symlink sys.config.orig to sys.config. Keep it as + #a symlink. + if [ -L "$orig_relx_config_path" ]; then + mv "$orig_relx_config_path" "$RELX_CONFIG_PATH" + fi fi # Extract the target node name from node.args -NAME_ARG=`egrep '^\-s?name' $RUNNER_ETC_DIR/vm.args` +NAME_ARG=$(egrep '^-s?name' "$VMARGS_PATH" || true) if [ -z "$NAME_ARG" ]; then echo "vm.args needs to have either -name or -sname parameter." exit 1 fi -NODE_NAME=${NAME_ARG##* } + +# Extract the name type and name from the NAME_ARG for REMSH +NAME_TYPE="$(echo "$NAME_ARG" | awk '{print $1}')" +NAME="$(echo "$NAME_ARG" | awk '{print $2}')" + +PIPE_DIR="${PIPE_DIR:-/tmp/erl_pipes/$NAME/}" # Extract the target cookie -COOKIE_ARG=`grep '^\-setcookie' $RUNNER_ETC_DIR/vm.args` +COOKIE_ARG="$(grep '^-setcookie' "$VMARGS_PATH" || true)" if [ -z "$COOKIE_ARG" ]; then echo "vm.args needs to have a -setcookie parameter." exit 1 fi -# Identify the script name -SCRIPT=`basename $0` +# Extract cookie name from COOKIE_ARG +COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')" -# Parse out release and erts info -START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` -ERTS_VSN=${START_ERL% *} -APP_VSN=${START_ERL#* } +find_erts_dir +export ROOTDIR="$RELEASE_ROOT_DIR" +export BINDIR="$ERTS_DIR/bin" +export EMU="beam" +export PROGNAME="erl" +export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH" +ERTS_LIB_DIR="$ERTS_DIR/../lib" +MNESIA_DATA_DIR="$ROOTDIR/data/mnesia/$NAME" -# Add ERTS bin dir to our path -ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin +cd "$ROOTDIR" -# Setup command to control the node -NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" -NODETOOL_LITE="$ERTS_PATH/escript $ERTS_PATH/nodetool" - -# Common functions - -# Ping node without allowing nodetool to take stdin -ping_node() { - $NODETOOL ping < /dev/null -} - -# Set the PID global variable, return 1 on error -get_pid() { - PID=`$NODETOOL getpid < /dev/null` - ES=$? - if [ "$ES" -ne 0 ]; then - echo "Node is not running!" - return 1 - fi - - # don't allow empty or init pid's - if [ -z $PID ] || [ "$PID" -le 1 ]; then - return 1 - fi - - return 0 -} - - -# Scrape out SSL distribution config info from vm.args into $SSL_DIST_CONFIG -rm -f $SSL_DIST_CONFIG -sed -n '/Begin SSL distribution items/,/End SSL distribution items/p' \ - $RUNNER_ETC_DIR/vm.args > $SSL_DIST_CONFIG +# User can specify an sname without @hostname +# This will fail when creating remote shell +# So here we check for @ and add @hostname if missing +case $NAME in + *@*) + # Nothing to do + ;; + *) + NAME=$NAME@$(relx_get_nodename) + ;; +esac # Check the first argument for instructions case "$1" in - start) - # Make sure there is not already a node running - RES=`ping_node` - if [ "$RES" = "pong" ]; then - echo "Node is already running!" - exit 1 - fi - # Sanity check the emqttd.config file - RES=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/emqttd.config` - if [ $? != 0 ]; then - echo "Error reading $RUNNER_ETC_DIR/emqttd.config" - echo $RES - exit 1 - fi - HEART_COMMAND="$RUNNER_SCRIPT_DIR/$SCRIPT start" - export HEART_COMMAND - mkdir -p $PIPE_DIR - $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR \ - "exec $RUNNER_SCRIPT_DIR/$SCRIPT console" 2>&1 + start|start_boot) - # Wait for the node to come up. We can't just ping it because - # distributed erlang comes up for a second before emqttd crashes - # (eg. in the case of an unwriteable disk). Once the node comes - # up we check for the node watcher process. If that's running - # then we assume things are good enough. This will at least let - # the user know when emqttd is crashing right after startup. - WAIT=${WAIT_FOR_ERLANG:-15} - while [ $WAIT -gt 0 ]; do - WAIT=`expr $WAIT - 1` - sleep 1 - RES=`ping_node` - if [ "$?" -ne 0 ]; then - continue - fi - echo "emqttd is started successfully!" - exit 0 - done - echo "emqttd failed to start within ${WAIT_FOR_ERLANG:-15} seconds," - echo "see the output of 'emqttd console' for more information." - echo "If you want to wait longer, set the environment variable" - echo "WAIT_FOR_ERLANG to the number of seconds to wait." - exit 1 + # Make sure there is not already a node running + #RES=`$NODETOOL ping` + #if [ "$RES" = "pong" ]; then + # echo "Node is already running!" + # exit 1 + #fi + # Save this for later. + CMD=$1 + case "$1" in + start) + shift + START_OPTION="console" + HEART_OPTION="start" + ;; + start_boot) + shift + START_OPTION="console_boot" + HEART_OPTION="start_boot" + ;; + esac + RUN_PARAM="$@" + + # Set arguments for the heart command + set -- "$SCRIPT_DIR/$REL_NAME" "$HEART_OPTION" + [ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM" + + # Export the HEART_COMMAND + HEART_COMMAND="$RELEASE_ROOT_DIR/bin/$REL_NAME $CMD" + export HEART_COMMAND + + mkdir -p "$PIPE_DIR" + + "$BINDIR/run_erl" -daemon "$PIPE_DIR" "$RUNNER_LOG_DIR" \ + "$(relx_start_command)" ;; stop) - UNAME_S=`uname -s` - case $UNAME_S in - Darwin) - # Make sure we explicitly set this because iTerm.app doesn't for - # some reason. - COMMAND_MODE=unix2003 - esac - - # Get the PID from nodetool - get_pid - GPR=$? - if [ "$GPR" -ne 0 ] || [ -z $PID ]; then - exit $GPR - fi - - # Tell nodetool to initiate a stop - $NODETOOL stop - ES=$? - if [ "$ES" -ne 0 ]; then - exit $ES - fi - # Wait for the node to completely stop... - while `kill -s 0 $PID 2>/dev/null`; + PID="$(relx_get_pid)" + if ! relx_nodetool "stop"; then + exit 1 + fi + while $(kill -s 0 "$PID" 2>/dev/null); do sleep 1 done @@ -205,117 +258,214 @@ case "$1" in restart) ## Restart the VM without exiting the process - $NODETOOL restart - ES=$? - if [ "$ES" -ne 0 ]; then - exit $ES + if ! relx_nodetool "restart"; then + exit 1 fi ;; reboot) ## Restart the VM completely (uses heart to restart it) - $NODETOOL reboot - ES=$? - if [ "$ES" -ne 0 ]; then - exit $ES + if ! relx_nodetool "reboot"; then + exit 1 + fi + ;; + + pid) + ## Get the VM's pid + if ! relx_get_pid; then + exit 1 fi ;; ping) ## See if the VM is alive - ping_node - ES=$? - if [ "$ES" -ne 0 ]; then - exit $ES + if ! relx_nodetool "ping"; then + exit 1 + fi + ;; + + escript) + ## Run an escript under the node's environment + if ! relx_escript $@; then + exit 1 fi ;; attach) - if [ "$2" = "-f" ]; then - echo "Forcing connection..." - else - # Make sure a node is running - RES=`ping_node` - ES=$? - if [ "$ES" -ne 0 ]; then - echo "Node is not running!" - exit $ES - fi + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 fi shift - exec $ERTS_PATH/to_erl $PIPE_DIR + exec "$BINDIR/to_erl" "$PIPE_DIR" ;; - console) - RES=`ping_node` - if [ "$RES" = "pong" ]; then - echo "Node is already running - use '$SCRIPT attach' instead" + remote_console) + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" exit 1 fi - # Sanity check the emqttd.config file - RES=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/emqttd.config` - if [ $? != 0 ]; then - echo "Error reading $RUNNER_ETC_DIR/emqttd.config" - echo $RES + + shift + relx_rem_sh + ;; + + upgrade|downgrade|install) + if [ -z "$2" ]; then + echo "Missing package argument" + echo "Usage: $REL_NAME $1 {package base name}" + echo "NOTE {package base name} MUST NOT include the .tar.gz suffix" exit 1 fi + + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 + fi + + exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \ + "install" "$REL_NAME" "$NAME_TYPE" "$NAME" "$COOKIE" "$2" + ;; + + unpack) + if [ -z "$2" ]; then + echo "Missing package argument" + echo "Usage: $REL_NAME $1 {package base name}" + echo "NOTE {package base name} MUST NOT include the .tar.gz suffix" + exit 1 + fi + + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 + fi + + exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \ + "unpack" "$REL_NAME" "$NAME_TYPE" "$NAME" "$COOKIE" "$2" + ;; + + console|console_clean|console_boot) + # .boot file typically just $REL_NAME (ie, the app name) + # however, for debugging, sometimes start_clean.boot is useful. + # For e.g. 'setup', one may even want to name another boot script. + case "$1" in + console) + if [ -f "$REL_DIR/$REL_NAME.boot" ]; then + BOOTFILE="$REL_DIR/$REL_NAME" + else + BOOTFILE="$REL_DIR/start" + fi + ;; + console_clean) + BOOTFILE="$ROOTDIR/bin/start_clean" + ;; + console_boot) + shift + BOOTFILE="$1" + shift + ;; + esac # Setup beam-required vars - ROOTDIR=$RUNNER_BASE_DIR - ERL_LIBS=$ROOTDIR/plugins - BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin - EMU=beam - PROGNAME=`echo $0 | sed 's/.*\///'` - # Setup Mnesia Dir - MNESIA_DIR="$RUNNER_DATA_DIR/mnesia/$NODE_NAME" - CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$SCRIPT \ - -embedded -config $RUNNER_ETC_DIR/emqttd.config \ - -pa $RUNNER_LIB_DIR/basho-patches \ - -mnesia dir "\"${MNESIA_DIR}\"" \ - -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}" + EMU="beam" + PROGNAME="${0#*/}" + export EMU - export ROOTDIR - export ERL_LIBS - export BINDIR export PROGNAME + # Store passed arguments since they will be erased by `set` + ARGS="$@" + + # Build an array of arguments to pass to exec later on + # Build it here because this command will be used for logging. + set -- "$BINDIR/erlexec" -boot "$BOOTFILE" -mode "$CODE_LOADING_MODE" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -config "$RELX_CONFIG_PATH" \ + -mnesia dir "\"${MNESIA_DATA_DIR}\"" \ + -args_file "$VMARGS_PATH" + # Dump environment info for logging purposes - echo "Exec: $CMD" + echo "Exec: $@" -- ${1+$ARGS} echo "Root: $ROOTDIR" # Log the startup - logger -t "$SCRIPT[$$]" "Starting up" + echo "$RELEASE_ROOT_DIR" + logger -t "$REL_NAME[$$]" "Starting up" # Start the VM - exec $CMD + exec "$@" -- ${1+$ARGS} ;; - chkconfig) - RES=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/emqttd.config` - if [ $? != 0 ]; then - echo "Error reading $RUNNER_ETC_DIR/emqttd.config" - echo $RES + + foreground) + # start up the release in the foreground for use by runit + # or other supervision services + + [ -f "$REL_DIR/$REL_NAME.boot" ] && BOOTFILE="$REL_NAME" || BOOTFILE=start + FOREGROUNDOPTIONS="-noshell -noinput +Bd" + + # Setup beam-required vars + EMU=beam + PROGNAME="${0#*/}" + + export EMU + export PROGNAME + + # Store passed arguments since they will be erased by `set` + ARGS="$@" + + # Build an array of arguments to pass to exec later on + # Build it here because this command will be used for logging. + set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \ + -boot "$REL_DIR/$BOOTFILE" -mode "$CODE_LOADING_MODE" -config "$RELX_CONFIG_PATH" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -mnesia dir "\"${MNESIA_DATA_DIR}\"" \ + -args_file "$VMARGS_PATH" + + # Dump environment info for logging purposes + echo "Exec: $@" -- ${1+$ARGS} + echo "Root: $ROOTDIR" + + # Start the VM + exec "$@" -- ${1+$ARGS} + ;; + rpc) + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" exit 1 fi - echo "config is OK" - ;; - escript) + shift - $ERTS_PATH/escript "$@" + + relx_nodetool rpc $@ ;; - version) - echo $RIAK_VERSION - ;; - getpid) - # Get the PID from nodetool - get_pid - ES=$? - if [ "$ES" -ne 0 ] || [ -z $PID ]; then - exit $ES + rpcterms) + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 fi - echo $PID + + shift + + relx_nodetool rpcterms $@ + ;; + eval) + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 + fi + + shift + relx_nodetool "eval" $@ ;; *) - echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach|chkconfig|escript|version|getpid}" + echo "Usage: $REL_NAME {start|start_boot |foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot |attach|remote_console|upgrade|escript|rpc|rpcterms|eval}" exit 1 ;; esac diff --git a/bin/emqttd_ctl b/bin/emqttd_ctl index 4292b893e..a2d1a28bc 100755 --- a/bin/emqttd_ctl +++ b/bin/emqttd_ctl @@ -2,90 +2,82 @@ # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et -# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. -if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then - POSIX_SHELL="true" - export POSIX_SHELL - # To support 'whoami' add /usr/ucb to path - PATH=/usr/ucb:$PATH - export PATH - exec /usr/bin/ksh $0 "$@" -fi -unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well +set -e -RUNNER_SCRIPT_DIR={{runner_script_dir}} -RUNNER_SCRIPT=${0##*/} +SCRIPT=$(readlink $0 || true) +if [ -z $SCRIPT ]; then + SCRIPT=$0 +fi; +SCRIPT_DIR="$(cd `dirname "$SCRIPT"` && pwd -P)" +RELEASE_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)" +REL_NAME="emqttd" +REL_VSN="{{ rel_vsn }}" +ERTS_VSN="{{ erts_vsn }}" +REL_DIR="$RELEASE_ROOT_DIR/releases/$REL_VSN" +ERL_OPTS="{{ erl_opts }}" +RUNNER_LOG_DIR="${RUNNER_LOG_DIR:-$RELEASE_ROOT_DIR/log}" -RUNNER_BASE_DIR={{runner_base_dir}} -RUNNER_ETC_DIR={{runner_etc_dir}} -RUNNER_LIB_DIR={{platform_lib_dir}} -RUNNER_LOG_DIR={{runner_log_dir}} -RUNNER_USER={{runner_user}} - -WHOAMI=$(whoami) - -# Make sure this script is running as the appropriate user -if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then - type sudo > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 - exit 1 +find_erts_dir() { + __erts_dir="$RELEASE_ROOT_DIR/erts-$ERTS_VSN" + if [ -d "$__erts_dir" ]; then + ERTS_DIR="$__erts_dir"; + ROOTDIR="$RELEASE_ROOT_DIR" + else + __erl="$(which erl)" + code="io:format(\"~s\", [code:root_dir()]), halt()." + __erl_root="$("$__erl" -noshell -eval "$code")" + ERTS_DIR="$__erl_root/erts-$ERTS_VSN" + ROOTDIR="$__erl_root" fi - echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2 - exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ -fi +} -# Make sure CWD is set to runner base dir -cd $RUNNER_BASE_DIR +relx_get_nodename() { + id="longname$(relx_gen_id)-${NAME}" + "$BINDIR/erl" -boot start_clean -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell ${NAME_TYPE} $id +} + +# Control a node +relx_nodetool() { + command="$1"; shift + + "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \ + -setcookie "$COOKIE" "$command" $@ +} + +# Use $CWD/vm.args if exists, otherwise releases/VSN/vm.args +if [ -z "$VMARGS_PATH" ]; then + if [ -f "$RELEASE_ROOT_DIR/vm.args" ]; then + VMARGS_PATH="$RELEASE_ROOT_DIR/vm.args" + else + VMARGS_PATH="$REL_DIR/vm.args" + fi +fi # Extract the target node name from node.args -NAME_ARG=`egrep "^ *-s?name" $RUNNER_ETC_DIR/vm.args` +NAME_ARG=$(egrep '^-s?name' "$VMARGS_PATH" || true) if [ -z "$NAME_ARG" ]; then echo "vm.args needs to have either -name or -sname parameter." exit 1 fi -# Learn how to specify node name for connection from remote nodes -echo "$NAME_ARG" | grep '^-sname' > /dev/null 2>&1 -if [ "X$?" = "X0" ]; then - NAME_PARAM="-sname" - NAME_HOST="" -else - NAME_PARAM="-name" - echo "$NAME_ARG" | grep '@.*' > /dev/null 2>&1 - if [ "X$?" = "X0" ]; then - NAME_HOST=`echo "${NAME_ARG}" | sed -e 's/.*\(@.*\)$/\1/'` - else - NAME_HOST="" - fi -fi +# Extract the name type and name from the NAME_ARG for REMSH +NAME_TYPE="$(echo "$NAME_ARG" | awk '{print $1}')" +NAME="$(echo "$NAME_ARG" | awk '{print $2}')" # Extract the target cookie -COOKIE_ARG=`grep '\-setcookie' $RUNNER_ETC_DIR/vm.args` +COOKIE_ARG="$(grep '^-setcookie' "$VMARGS_PATH" || true)" if [ -z "$COOKIE_ARG" ]; then echo "vm.args needs to have a -setcookie parameter." exit 1 fi -# Identify the script name -SCRIPT=`basename $0` +# Extract cookie name from COOKIE_ARG +COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')" -# Parse out release and erts info -START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` -ERTS_VSN=${START_ERL% *} -APP_VSN=${START_ERL#* } +find_erts_dir +export ROOTDIR="$RELEASE_ROOT_DIR" +export BINDIR="$ERTS_DIR/bin" +cd "$ROOTDIR" -# Add ERTS bin dir to our path -ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin - -# Setup command to control the node -NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" - -RES=`$NODETOOL ping` -if [ "$RES" != "pong" ]; then - echo "Node is not running!" - exit 1 -fi - -$NODETOOL rpc emqttd_ctl run $@ +relx_nodetool rpc emqttd_ctl run $@ diff --git a/bin/emqttd_top b/bin/emqttd_top deleted file mode 100755 index 24533c436..000000000 --- a/bin/emqttd_top +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/sh -# -*- tab-width:4;indent-tabs-mode:nil -*- -# ex: ts=4 sw=4 et - -# /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. -if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then - POSIX_SHELL="true" - export POSIX_SHELL - # To support 'whoami' add /usr/ucb to path - PATH=/usr/ucb:$PATH - export PATH - exec /usr/bin/ksh $0 "$@" -fi -unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well - -RUNNER_SCRIPT_DIR={{runner_script_dir}} -RUNNER_SCRIPT=${0##*/} - -RUNNER_BASE_DIR={{runner_base_dir}} -RUNNER_ETC_DIR={{runner_etc_dir}} -RUNNER_LIB_DIR={{platform_lib_dir}} -RUNNER_USER={{runner_user}} - -WHOAMI=$(whoami) - -# Make sure this script is running as the appropriate user -if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then - type sudo > /dev/null 2>&1 - if [ $? -ne 0 ]; then - echo "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2 - exit 1 - fi - echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2 - exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ -fi - -# Make sure CWD is set to runner base dir -cd $RUNNER_BASE_DIR - -# Extract the target node name from node.args -NAME_ARG=`egrep "^ *-s?name" $RUNNER_ETC_DIR/vm.args` -if [ -z "$NAME_ARG" ]; then - echo "vm.args needs to have either -name or -sname parameter." - exit 1 -fi - -# Learn how to specify node name for connection from remote nodes -echo "$NAME_ARG" | grep '^-sname' > /dev/null 2>&1 -if [ "X$?" = "X0" ]; then - NAME_PARAM="-sname" - NAME_HOST="" -else - NAME_PARAM="-name" - echo "$NAME_ARG" | grep '@.*' > /dev/null 2>&1 - if [ "X$?" = "X0" ]; then - NAME_HOST=`echo "${NAME_ARG}" | sed -e 's/.*\(@.*\)$/\1/'` - else - NAME_HOST="" - fi -fi - -# Extract the target cookie -COOKIE_ARG=`grep '\-setcookie' $RUNNER_ETC_DIR/vm.args` -if [ -z "$COOKIE_ARG" ]; then - echo "vm.args needs to have a -setcookie parameter." - exit 1 -fi - -# Identify the script name -SCRIPT=`basename $0` - -# Parse out release and erts info -START_ERL=`cat $RUNNER_BASE_DIR/releases/start_erl.data` -ERTS_VSN=${START_ERL% *} -APP_VSN=${START_ERL#* } - -# Add ERTS bin dir to our path -ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin - -NODE_NAME=${NAME_ARG#* } - -# Setup command to control the node -NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" - -RES=`$NODETOOL ping` -if [ "$RES" != "pong" ]; then - echo "Node is not running!" - exit 1 -fi - -case "$1" in - runtime) - SORTBY="runtime" - ;; - reductions) - SORTBY="reductions" - ;; - memory) - SORTBY="memory" - ;; - msg_q) - SORTBY="msg_q" - ;; - *) - echo "Usage: $SCRIPT {runtime | reductions | memory | msg_q}" - exit 1 - ;; -esac - -MYPID=$$ -ETOP_ARGS="-sort $SORTBY -interval 10 -lines 50 -tracing off" -$ERTS_PATH/erl -noshell -noinput \ - -pa $RUNNER_LIB_DIR/basho-patches \ - -hidden $NAME_PARAM emqttd_top$MYPID$NAME_HOST $COOKIE_ARG \ - -s etop -s erlang halt -output text \ - -node $NODE_NAME $ETOP_ARGS - diff --git a/bin/install_upgrade.escript b/bin/install_upgrade.escript deleted file mode 100644 index 56cea1963..000000000 --- a/bin/install_upgrade.escript +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env escript -%%! -noshell -noinput -%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- -%% ex: ft=erlang ts=4 sw=4 et - --define(TIMEOUT, 60000). --define(INFO(Fmt,Args), io:format(Fmt,Args)). - -main([NodeName, Cookie, ReleasePackage]) -> - TargetNode = start_distribution(NodeName, Cookie), - {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, - [ReleasePackage], ?TIMEOUT), - ?INFO("Unpacked Release ~p~n", [Vsn]), - {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, - check_install_release, [Vsn], ?TIMEOUT), - {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, - install_release, [Vsn], ?TIMEOUT), - ?INFO("Installed Release ~p~n", [Vsn]), - ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), - ?INFO("Made Release ~p Permanent~n", [Vsn]); -main(_) -> - init:stop(1). - -start_distribution(NodeName, Cookie) -> - MyNode = make_script_node(NodeName), - {ok, _Pid} = net_kernel:start([MyNode, shortnames]), - erlang:set_cookie(node(), list_to_atom(Cookie)), - TargetNode = make_target_node(NodeName), - case {net_kernel:hidden_connect_node(TargetNode), - net_adm:ping(TargetNode)} of - {true, pong} -> - ok; - {_, pang} -> - io:format("Node ~p not responding to pings.\n", [TargetNode]), - init:stop(1) - end, - TargetNode. - -make_target_node(Node) -> - [_, Host] = string:tokens(atom_to_list(node()), "@"), - list_to_atom(lists:concat([Node, "@", Host])). - -make_script_node(Node) -> - list_to_atom(lists:concat([Node, "_upgrader_", os:getpid()])). diff --git a/bin/install_upgrade_escript b/bin/install_upgrade_escript new file mode 100755 index 000000000..4abce858d --- /dev/null +++ b/bin/install_upgrade_escript @@ -0,0 +1,143 @@ +#!/usr/bin/env escript +%%! -noshell -noinput +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et + +-define(TIMEOUT, 300000). +-define(INFO(Fmt,Args), io:format(Fmt,Args)). + +%% Unpack or upgrade to a new tar.gz release +main(["unpack", RelName, NameTypeArg, NodeName, Cookie, VersionArg]) -> + TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), + WhichReleases = which_releases(TargetNode), + Version = parse_version(VersionArg), + case proplists:get_value(Version, WhichReleases) of + undefined -> + %% not installed, so unpack tarball: + ?INFO("Release ~s not found, attempting to unpack releases/~s/~s.tar.gz~n",[Version,Version,RelName]), + ReleasePackage = Version ++ "/" ++ RelName, + case rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackage], ?TIMEOUT) of + {ok, Vsn} -> + ?INFO("Unpacked successfully: ~p~n", [Vsn]); + {error, UnpackReason} -> + print_existing_versions(TargetNode), + ?INFO("Unpack failed: ~p~n",[UnpackReason]), + erlang:halt(2) + end; + old -> + %% no need to unpack, has been installed previously + ?INFO("Release ~s is marked old, switching to it.~n",[Version]); + unpacked -> + ?INFO("Release ~s is already unpacked, now installing.~n",[Version]); + current -> + ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]); + permanent -> + ?INFO("Release ~s is already installed, and set permanent.~n",[Version]) + end; +main(["install", RelName, NameTypeArg, NodeName, Cookie, VersionArg]) -> + TargetNode = start_distribution(NodeName, NameTypeArg, Cookie), + WhichReleases = which_releases(TargetNode), + Version = parse_version(VersionArg), + case proplists:get_value(Version, WhichReleases) of + undefined -> + %% not installed, so unpack tarball: + ?INFO("Release ~s not found, attempting to unpack releases/~s/~s.tar.gz~n",[Version,Version,RelName]), + ReleasePackage = Version ++ "/" ++ RelName, + case rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackage], ?TIMEOUT) of + {ok, Vsn} -> + ?INFO("Unpacked successfully: ~p~n", [Vsn]), + install_and_permafy(TargetNode, RelName, Vsn); + {error, UnpackReason} -> + print_existing_versions(TargetNode), + ?INFO("Unpack failed: ~p~n",[UnpackReason]), + erlang:halt(2) + end; + old -> + %% no need to unpack, has been installed previously + ?INFO("Release ~s is marked old, switching to it.~n",[Version]), + install_and_permafy(TargetNode, RelName, Version); + unpacked -> + ?INFO("Release ~s is already unpacked, now installing.~n",[Version]), + install_and_permafy(TargetNode, RelName, Version); + current -> %% installed and in-use, just needs to be permanent + ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]), + permafy(TargetNode, RelName, Version); + permanent -> + ?INFO("Release ~s is already installed, and set permanent.~n",[Version]) + end; +main(_) -> + erlang:halt(1). + +parse_version(V) when is_list(V) -> + hd(string:tokens(V,"/")). + +install_and_permafy(TargetNode, RelName, Vsn) -> + case rpc:call(TargetNode, release_handler, check_install_release, [Vsn], ?TIMEOUT) of + {ok, _OtherVsn, _Desc} -> + ok; + {error, Reason} -> + ?INFO("ERROR: release_handler:check_install_release failed: ~p~n",[Reason]), + erlang:halt(3) + end, + case rpc:call(TargetNode, release_handler, install_release, [Vsn], ?TIMEOUT) of + {ok, _, _} -> + ?INFO("Installed Release: ~s~n", [Vsn]), + permafy(TargetNode, RelName, Vsn), + ok; + {error, {no_such_release, Vsn}} -> + VerList = + iolist_to_binary( + [io_lib:format("* ~s\t~s~n",[V,S]) || {V,S} <- which_releases(TargetNode)]), + ?INFO("Installed versions:~n~s", [VerList]), + ?INFO("ERROR: Unable to revert to '~s' - not installed.~n", [Vsn]), + erlang:halt(2) + end. + +permafy(TargetNode, RelName, Vsn) -> + ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT), + file:copy(filename:join(["bin", RelName++"-"++Vsn]), + filename:join(["bin", RelName])), + ?INFO("Made release permanent: ~p~n", [Vsn]), + ok. + +which_releases(TargetNode) -> + R = rpc:call(TargetNode, release_handler, which_releases, [], ?TIMEOUT), + [ {V, S} || {_,V,_, S} <- R ]. + +print_existing_versions(TargetNode) -> + VerList = iolist_to_binary([ + io_lib:format("* ~s\t~s~n",[V,S]) + || {V,S} <- which_releases(TargetNode) ]), + ?INFO("Installed versions:~n~s", [VerList]). + +start_distribution(NodeName, NameTypeArg, Cookie) -> + MyNode = make_script_node(NodeName), + {ok, _Pid} = net_kernel:start([MyNode, get_name_type(NameTypeArg)]), + erlang:set_cookie(node(), list_to_atom(Cookie)), + TargetNode = list_to_atom(NodeName), + case {net_kernel:connect_node(TargetNode), + net_adm:ping(TargetNode)} of + {true, pong} -> + ok; + {_, pang} -> + io:format("Node ~p not responding to pings.\n", [TargetNode]), + erlang:halt(1) + end, + {ok, Cwd} = file:get_cwd(), + ok = rpc:call(TargetNode, file, set_cwd, [Cwd], ?TIMEOUT), + TargetNode. + +make_script_node(Node) -> + [Name, Host] = string:tokens(Node, "@"), + list_to_atom(lists:concat([Name, "_upgrader_", os:getpid(), "@", Host])). + +%% get name type from arg +get_name_type(NameTypeArg) -> + case NameTypeArg of + "-sname" -> + shortnames; + _ -> + longnames + end. diff --git a/bin/nodetool b/bin/nodetool index 09c2b86ef..d76c228b1 100755 --- a/bin/nodetool +++ b/bin/nodetool @@ -1,5 +1,4 @@ #!/usr/bin/env escript - %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- %% ex: ft=erlang ts=4 sw=4 et %% ------------------------------------------------------------------- @@ -38,8 +37,8 @@ main(Args) -> {error, {Line, Mod, Term}} -> io:format(standard_error, ["Error on line ", file:format_error({Line, Mod, Term}), "\n"], []), halt(1); - {error, R} -> - io:format(standard_error, ["Error reading config file: ", file:format_error(R), "\n"], []), + {error, Error} -> + io:format(standard_error, ["Error reading config file: ", file:format_error(Error), "\n"], []), halt(1) end; _ -> @@ -94,20 +93,48 @@ main(Args) -> end; ["rpcterms", Module, Function, ArgsAsString] -> case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), - consult(ArgsAsString), 60000) of + consult(lists:flatten(ArgsAsString)), 60000) of {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), halt(1); Other -> io:format("~p\n", [Other]) end; + ["eval" | ListOfArgs] -> + % shells may process args into more than one, and end up stripping + % spaces, so this converts all of that to a single string to parse + String = binary_to_list( + list_to_binary( + string:join(ListOfArgs," ") + ) + ), + + % then just as a convenience to users, if they forgot a trailing + % '.' add it for them. + Normalized = + case lists:reverse(String) of + [$. | _] -> String; + R -> lists:reverse([$. | R]) + end, + + % then scan and parse the string + {ok, Scanned, _} = erl_scan:string(Normalized), + {ok, Parsed } = erl_parse:parse_exprs(Scanned), + + % and evaluate it on the remote node + case rpc:call(TargetNode, erl_eval, exprs, [Parsed, [] ]) of + {value, Value, _} -> + io:format ("~p\n",[Value]); + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1) + end; Other -> io:format("Other: ~p\n", [Other]), - io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms}\n") + io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval [Terms]} [RPC]\n") end, net_kernel:stop(). - process_args([], Acc, TargetNode) -> {lists:reverse(Acc), TargetNode}; process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> @@ -126,7 +153,7 @@ process_args([Arg | Rest], Acc, Opts) -> start_epmd() -> - [] = os:cmd(epmd_path() ++ " -daemon"), + [] = os:cmd("\"" ++ epmd_path() ++ "\" -daemon"), ok. epmd_path() -> @@ -163,7 +190,6 @@ append_node_suffix(Name, Suffix) -> list_to_atom(lists:concat([Node, Suffix, os:getpid()])) end. - %% %% Given a string or binary, parse it into a list of terms, ala file:consult/0 %% @@ -188,7 +214,6 @@ consult(Cont, Str, Acc) -> consult(Cont1, eof, Acc) end. - %% %% Validation functions for checking the emqttd.config %% @@ -211,4 +236,3 @@ print_issue({warning, Warning}) -> print_issue({error, Error}) -> io:format(standard_error, "Error in emqttd.config: ~s~n", [Error]). - diff --git a/etc/emqttd.conf b/etc/emqttd.conf index 04daa1712..927bc1d78 100644 --- a/etc/emqttd.conf +++ b/etc/emqttd.conf @@ -1,3 +1,27 @@ +%%=================================================================== +%% +%% Config file for emqttd 2.0 +%% +%% Erlang Term Syntax: +%% +%% {}: Tuple, usually {Key, Value} +%% []: List, seperated by comma +%% %%: comment +%% +%%=================================================================== + +%%-------------------------------------------------------------------- +%% MQTT Protocol +%%-------------------------------------------------------------------- + +%% Max ClientId Length Allowed. +{mqtt_max_clientid_len, 512}. + +%% Max Packet Size Allowed, 64K by default. +{mqtt_max_packet_size, 65536}. + +%% Client Idle Timeout. +{mqtt_client_idle_timeout, 30}. % Second %%-------------------------------------------------------------------- %% Authentication @@ -11,7 +35,7 @@ {auth, username, [{passwd, "etc/passwd.conf"}, {passwd_hash, plain}]}. %% Authentication with clientId -{auth, clientid, [{clients, "etc/client.config"}, {password, no}]}. +{auth, clientid, [{config, "etc/client.config"}, {password, no}]}. %%-------------------------------------------------------------------- %% ACL @@ -42,25 +66,12 @@ {retained_max_playload_size, 65536}. %%-------------------------------------------------------------------- -%% MQTT Protocol -%%-------------------------------------------------------------------- - -%% Max ClientId Length Allowed. -{mqtt_max_clientid_len, 512}. - -%% Max Packet Size Allowed, 64K by default. -{mqtt_max_packet_size, 65536}. - -%% Socket Idle Timeout. -{mqtt_client_idle_timeout, 30}. % Seconds - -%%-------------------------------------------------------------------- -%% MQTT Session +%% Session %%-------------------------------------------------------------------- %% Max number of QoS 1 and 2 messages that can be “inflight” at one time. %% 0 means no limit -{session_max_inflight, 100}. +{session_max_inflight, 100}. %% Retry interval for redelivering QoS1/2 messages. {session_unack_retry_interval, 60}. @@ -101,7 +112,13 @@ {queue_qos0, true}. %%-------------------------------------------------------------------- -%% Listeners +%% Zone +%%-------------------------------------------------------------------- + +{zone, admin, []}. + +%%-------------------------------------------------------------------- +%% Listener %%-------------------------------------------------------------------- %% Plain MQTT @@ -112,6 +129,9 @@ %% Maximum number of concurrent clients {max_clients, 512}, + %% Mount point prefix + %% {mount_point, "prefix/"}, + %% Socket Access Control {access, [{allow, all}]}, @@ -132,7 +152,7 @@ ]} ]}. -%% MQTT SSL +%% MQTT/SSL {listener, mqtts, 8883, [ %% Size of acceptor pool {acceptors, 4}, @@ -179,8 +199,12 @@ %% PubSub and Router. Default should be scheduler numbers. {pubsub_pool_size, 8}. +%%-------------------------------------------------------------------- +%% Routing +%%-------------------------------------------------------------------- + %% Route aging time(seconds) -{pubsub_routing_age, 5}. +{routing_age, 5}. %%-------------------------------------------------------------------- %% Bridge @@ -196,35 +220,22 @@ %% Plugins %%------------------------------------------------------------------- -%% Plugins Dir -{plugins_dir, "./plugins"}. - %% File to store loaded plugin names. -{plugins_loaded_file, "./data/loaded_plugins"}. +{plugins_loaded_file, "data/loaded_plugins"}. -%%------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% Modules -%%------------------------------------------------------------------- +%%-------------------------------------------------------------------- -%% Client presence management module. Publish presence messages when client connected or disconnected +%% Client presence management module. Publish presence messages when +%% client connected or disconnected. {module, presence, [{qos, 0}]}. %% Subscribe topics automatically when client connected {module, subscription, [{"$queue/clients/$c", 1}, backend]}. %% [Rewrite](https://github.com/emqtt/emqttd/wiki/Rewrite) -{module, rewrite, [ - - %{topic, "x/#", [ - % {rewrite, "^x/y/(.+)$", "z/y/$1"}, - % {rewrite, "^x/(.+)$", "y/$1"} - %]}, - - %{topic, "y/+/z/#", [ - % {rewrite, "^y/(.+)/z/(.+)$", "y/z/$2"} - %]} - -]}. +{module, rewrite, [{config, "etc/rewrite.conf"}]}. %%------------------------------------------------------------------- %% Erlang System Monitor diff --git a/etc/rewrite.conf b/etc/rewrite.conf new file mode 100644 index 000000000..7c1a9094f --- /dev/null +++ b/etc/rewrite.conf @@ -0,0 +1,14 @@ + +%%-------------------------------------------------------------------- +%% [Rewrite](https://github.com/emqtt/emqttd/wiki/Rewrite) +%%-------------------------------------------------------------------- + +%{topic, "x/#", [ +% {rewrite, "^x/y/(.+)$", "z/y/$1"}, +% {rewrite, "^x/(.+)$", "y/$1"} +%]}. + +%{topic, "y/+/z/#", [ +% {rewrite, "^y/(.+)/z/(.+)$", "y/z/$2"} +%]}. + diff --git a/rel/vars.config b/rel/vars.config new file mode 100644 index 000000000..9e248b663 --- /dev/null +++ b/rel/vars.config @@ -0,0 +1,19 @@ +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et + +%% Platform-specific installation paths +{platform_bin_dir, "./bin"}. +{platform_data_dir, "./data"}. +{platform_etc_dir, "./etc"}. +{platform_lib_dir, "./lib"}. +{platform_log_dir, "./log"}. + +%% +%% bin/emqttd +%% +%% {runner_script_dir, "$(cd ${0%/*} && pwd)"}. +{runner_base_dir, "${RUNNER_SCRIPT_DIR%/*}"}. +{runner_etc_dir, "$RUNNER_BASE_DIR/etc"}. +{runner_log_dir, "$RUNNER_BASE_DIR/log"}. +{pipe_dir, "/tmp/$RUNNER_SCRIPT/"}. +{runner_user, ""}. diff --git a/relx b/relx new file mode 100755 index 000000000..24466871b Binary files /dev/null and b/relx differ diff --git a/relx.config b/relx.config new file mode 100644 index 000000000..817c82fa7 --- /dev/null +++ b/relx.config @@ -0,0 +1,41 @@ + +{release, {emqttd, "2.0"}, [ + sasl, + os_mon, + runtime_tools, + {mnesia, load}, + emqttd +]}. + +{include_src, false}. + +{extended_start_script, false}. + +{sys_config, "rel/sys.config"}. + +{vm_args, "rel/vm.args"}. + +{overlay_vars, "./rel/vars.config"}. + +{overlay, [ + {mkdir, "etc/"}, + {mkdir, "etc/ssl/"}, + {mkdir, "data/"}, + {mkdir, "data/mnesia"}, + {mkdir, "log/"}, + {copy, "etc/ssl/ssl.crt", "etc/ssl/ssl.crt"}, + {copy, "etc/ssl/ssl.key", "etc/ssl/ssl.key"}, + + {template, "bin/emqttd", "bin/emqttd"}, + {template, "bin/emqttd_ctl", "bin/emqttd_ctl"}, + {copy, "bin/nodetool", "bin/nodetool"}, + {copy, "bin/nodetool", "erts-\{\{erts_vsn\}\}/bin/nodetool"}, + {copy, "bin/install_upgrade_escript", "bin/install_upgrade_escript"}, + + {template, "etc/acl.conf", "etc/acl.conf"}, + {template, "etc/client.conf", "etc/client.conf"}, + {template, "etc/emqttd.conf", "etc/emqttd.conf"}, + {template, "etc/passwd.conf", "etc/passwd.conf"}, + {template, "etc/rewrite.conf", "etc/rewrite.conf"} +]}. + diff --git a/src/emqttd.app.src b/src/emqttd.app.src index 9b58ce3df..e4f9fcc49 100644 --- a/src/emqttd.app.src +++ b/src/emqttd.app.src @@ -5,7 +5,7 @@ {id, "emqttd"}, {modules, []}, {registered, []}, - {applications, [kernel, stdlib, gproc, esockd, mochiweb]}, + {applications, [kernel, stdlib, gproc, esockd, mochiweb, gen_logger, gen_conf]}, {mod, {emqttd_app, []}}, {env, []} ]}. diff --git a/src/emqttd_client.erl b/src/emqttd_client.erl index 9d29f4c68..6a3bee488 100644 --- a/src/emqttd_client.erl +++ b/src/emqttd_client.erl @@ -39,7 +39,7 @@ %% Client State -record(client_state, {connection, connname, peername, peerhost, peerport, await_recv, conn_state, rate_limit, parser_fun, - proto_state, packet_opts, keepalive}). + proto_state, packet_opts, keepalive, mountpoint}). -define(INFO_KEYS, [peername, peerhost, peerport, await_recv, conn_state]).