Feng 9 lat temu
rodzic
commit
710890dd22
14 zmienionych plików z 737 dodań i 505 usunięć
  1. 1 2
      .gitignore
  2. 378 228
      bin/emqttd
  3. 56 64
      bin/emqttd_ctl
  4. 0 117
      bin/emqttd_top
  5. 0 44
      bin/install_upgrade.escript
  6. 143 0
      bin/install_upgrade_escript
  7. 34 10
      bin/nodetool
  8. 49 38
      etc/emqttd.conf
  9. 14 0
      etc/rewrite.conf
  10. 19 0
      rel/vars.config
  11. BIN
      relx
  12. 41 0
      relx.config
  13. 1 1
      src/emqttd.app.src
  14. 1 1
      src/emqttd_client.erl

+ 1 - 2
.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/

+ 378 - 228
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
-
-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
+set -e
+
+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
+}
 
-# Make sure log directory exists
-mkdir -p $RUNNER_LOG_DIR
+# 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
+}
 
-# Make sure the data directory exists
-mkdir -p $PLATFORM_DATA_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
+}
 
-# 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 "!!!!"
-fi
+# 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}"
 
-# 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
-NODE_NAME=${NAME_ARG##* }
+    # Get the node's ticktime so that we use the same thing.
+    TICKTIME="$(relx_nodetool rpcterms net_kernel get_net_ticktime)"
 
-# 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
+    # 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
+}
 
-# Identify the script name
-SCRIPT=`basename $0`
+# Generate a random id
+relx_gen_id() {
+    od -t x -N 4 /dev/urandom | head -n1 | 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#* }
+# Control a node
+relx_nodetool() {
+    command="$1"; shift
 
-# Add ERTS bin dir to our path
-ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin
+    "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \
+                                -setcookie "$COOKIE" "$command" $@
+}
 
-# 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"
+# Run an escript in the node's environment
+relx_escript() {
+    shift; scriptpath="$1"; shift
+    export RELEASE_ROOT_DIR
 
-# Common functions
+    "$ERTS_DIR/bin/escript" "$ROOTDIR/$scriptpath" $@
+}
 
-# Ping node without allowing nodetool to take stdin
-ping_node() {
-    $NODETOOL ping < /dev/null
+# 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"
 }
 
-# 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
+# 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
 
-    # don't allow empty or init pid's
-    if [ -z $PID ] || [ "$PID" -le 1 ]; then
-        return 1
+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
 
-    return 0
-}
+    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"
+
+# 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
 
-# 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
+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' "$VMARGS_PATH" || true)
+if [ -z "$NAME_ARG" ]; then
+    echo "vm.args needs to have either -name or -sname parameter."
+    exit 1
+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}')"
+
+PIPE_DIR="${PIPE_DIR:-/tmp/erl_pipes/$NAME/}"
+
+# Extract the target cookie
+COOKIE_ARG="$(grep '^-setcookie' "$VMARGS_PATH" || true)"
+if [ -z "$COOKIE_ARG" ]; then
+    echo "vm.args needs to have a -setcookie parameter."
+    exit 1
+fi
+
+# Extract cookie name from COOKIE_ARG
+COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')"
+
+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"
+
+cd "$ROOTDIR"
+
+# 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
-
-        # 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
-        ;;
+    start|start_boot)
 
-    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
+        # 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="$@"
 
-        # Get the PID from nodetool
-        get_pid
-        GPR=$?
-        if [ "$GPR" -ne 0 ] || [ -z $PID ]; then
-            exit $GPR
-        fi
+        # Set arguments for the heart command
+        set -- "$SCRIPT_DIR/$REL_NAME" "$HEART_OPTION"
+        [ "$RUN_PARAM" ] && set -- "$@" "$RUN_PARAM"
 
-        # Tell nodetool to initiate a stop
-        $NODETOOL stop
-        ES=$?
-        if [ "$ES" -ne 0 ]; then
-            exit $ES
-        fi
+        # 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)
         # 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 "$BINDIR/to_erl" "$PIPE_DIR"
+        ;;
+
+    remote_console)
+        # 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
+        relx_rem_sh
         ;;
 
-    console)
-        RES=`ping_node`
-        if [ "$RES" = "pong" ]; then
-            echo "Node is already running - use '$SCRIPT attach' instead"
+    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
-        # 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
+
+        # 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 "$@" -- ${1+$ARGS}
+        ;;
+
+    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 $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
+    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
+    rpcterms)
+        # Make sure a node IS running
+        if ! relx_nodetool "ping" > /dev/null; then
+            echo "Node is not running!"
+            exit 1
+        fi
+
+        shift
+
+        relx_nodetool rpcterms $@
         ;;
-    getpid)
-        # Get the PID from nodetool
-        get_pid
-        ES=$?
-        if [ "$ES" -ne 0 ] || [ -z $PID ]; then
-            exit $ES
+    eval)
+        # 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 "eval" $@
         ;;
     *)
-        echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach|chkconfig|escript|version|getpid}"
+        echo "Usage: $REL_NAME {start|start_boot <file>|foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade|escript|rpc|rpcterms|eval}"
         exit 1
         ;;
 esac

+ 56 - 64
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
+
+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}"
+
+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
+}
 
-RUNNER_SCRIPT_DIR={{runner_script_dir}}
-RUNNER_SCRIPT=${0##*/}
+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
+}
 
-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}}
+# Control a node
+relx_nodetool() {
+    command="$1"; shift
 
-WHOAMI=$(whoami)
+    "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \
+                                -setcookie "$COOKIE" "$command" $@
+}
 
-# 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
+# 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
-    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`
+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`
-
-# 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
+# Extract cookie name from COOKIE_ARG
+COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')"
 
-# 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
+find_erts_dir
+export ROOTDIR="$RELEASE_ROOT_DIR"
+export BINDIR="$ERTS_DIR/bin"
+cd "$ROOTDIR"
 
-$NODETOOL rpc emqttd_ctl run $@
+relx_nodetool rpc emqttd_ctl run $@
 

+ 0 - 117
bin/emqttd_top

@@ -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
-

+ 0 - 44
bin/install_upgrade.escript

@@ -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()])).

+ 143 - 0
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.

+ 34 - 10
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]).
 
-

+ 49 - 38
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

+ 14 - 0
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"}
+%]}.
+

+ 19 - 0
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,        ""}.

BIN
relx


+ 41 - 0
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"}
+]}.
+

+ 1 - 1
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, []}
  ]}.

+ 1 - 1
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]).