diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..412eeda
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,22 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs diff=csharp
+*.sln merge=union
+*.csproj merge=union
+*.vbproj merge=union
+*.fsproj merge=union
+*.dbproj merge=union
+
+# Standard to msysgit
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b9d6bd9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,215 @@
+#################
+## Eclipse
+#################
+
+*.pydevproject
+.project
+.metadata
+bin/
+tmp/
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.classpath
+.settings/
+.loadpath
+
+# External tool builders
+.externalToolBuilders/
+
+# Locally stored "Eclipse launch configurations"
+*.launch
+
+# CDT-specific
+.cproject
+
+# PDT-specific
+.buildpath
+
+
+#################
+## Visual Studio
+#################
+
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.sln.docstates
+
+# Build results
+
+[Dd]ebug/
+[Rr]elease/
+x64/
+build/
+[Bb]in/
+[Oo]bj/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+*_i.c
+*_p.c
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.log
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opensdf
+*.sdf
+*.cachefile
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+*.ncrunch*
+.*crunch*.local.xml
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.Publish.xml
+*.pubxml
+
+# NuGet Packages Directory
+## TODO: If you have NuGet Package Restore enabled, uncomment the next line
+#packages/
+
+# Windows Azure Build Output
+csx
+*.build.csdef
+
+# Windows Store app package directory
+AppPackages/
+
+# Others
+sql/
+*.Cache
+ClientBin/
+[Ss]tyle[Cc]op.*
+~$*
+*~
+*.dbmdl
+*.[Pp]ublish.xml
+*.pfx
+*.publishsettings
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file to a newer
+# Visual Studio version. Backup files are not needed, because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+App_Data/*.mdf
+App_Data/*.ldf
+
+#############
+## Windows detritus
+#############
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Mac crap
+.DS_Store
+
+
+#############
+## Python
+#############
+
+*.py[co]
+
+# Packages
+*.egg
+*.egg-info
+dist/
+build/
+eggs/
+parts/
+var/
+sdist/
+develop-eggs/
+.installed.cfg
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+.tox
+
+#Translations
+*.mo
+
+#Mr Developer
+.mr.developer.cfg
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..3c6742f
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+deploy
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..217af47
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..3572571
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..22a5539
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..926bdd3
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f756e81
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/.idea/rebar.xml b/.idea/rebar.xml
new file mode 100644
index 0000000..c5a2371
--- /dev/null
+++ b/.idea/rebar.xml
@@ -0,0 +1,7 @@
+
+
+
+ C:\Users\zhaoxu-b\Documents\GitHub\deploy\rebar.cmd
+
+
+
diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..3b00020
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,125 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..9d32e50
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..dc9b534
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,794 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Abstraction issues
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ localhost
+ 5050
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+ 1383121281756
+ 1383121281756
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Erlang (deploy)|Erlang
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Erlang R15B03
+
+
+
+
+
+
+
+
+
+
+
+ deploy
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b695be8
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+Hot Upgrade Erlang Clusters, scp directory, file to clusters.( all in config file)
\ No newline at end of file
diff --git a/deploy.iml b/deploy.iml
new file mode 100644
index 0000000..ef121a4
--- /dev/null
+++ b/deploy.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..4ea3be7
--- /dev/null
+++ b/makefile
@@ -0,0 +1,35 @@
+.PHONY: rel deps
+
+PREFIX:=../
+DEST:=$(PREFIX)$(PROJECT)
+
+REBAR=./rebar
+
+all:
+ @$(REBAR) get-deps compile
+
+edoc:
+ @$(REBAR) doc
+
+test:
+ @rm -rf .eunit
+ @mkdir -p .eunit
+ @$(REBAR) skip_deps=true eunit
+
+rel:
+ @$(REBAR) generate
+clean:
+ @$(REBAR) clean
+clean_rel:
+ @rm -rf rel/deploy
+
+clean_all:
+ @$(REBAR) clean
+ @rm -rf rel/deploy
+
+build_plt:
+ @$(REBAR) build-plt
+
+dialyzer:
+ @$(REBAR) dialyze
+
diff --git a/rebar b/rebar
new file mode 100644
index 0000000..49cb508
Binary files /dev/null and b/rebar differ
diff --git a/rebar.cmd b/rebar.cmd
new file mode 100644
index 0000000..6c7a1ca
--- /dev/null
+++ b/rebar.cmd
@@ -0,0 +1,4 @@
+@echo off
+setlocal
+set rebarscript=%~f0
+escript.exe "%rebarscript:.cmd=%" %*
diff --git a/rebar.config b/rebar.config
new file mode 100644
index 0000000..2567b1a
--- /dev/null
+++ b/rebar.config
@@ -0,0 +1,5 @@
+{sub_dirs, ["rel"]}.
+
+{cover_enabled, true}.
+
+{erl_opts, [warnings_as_errors, debug_info]}.
\ No newline at end of file
diff --git a/rebuild.sh b/rebuild.sh
new file mode 100644
index 0000000..da41e7d
--- /dev/null
+++ b/rebuild.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+find . -name '._*' | xargs rm -f
+
+chmod +x rebar
+chmod +x rel/files/*
+chmod -x rel/files/*.config
+chmod -x rel/files/*.args
+
+make clean && make && make rel
diff --git a/rel/files/app.config b/rel/files/app.config
new file mode 100644
index 0000000..f57fe81
--- /dev/null
+++ b/rel/files/app.config
@@ -0,0 +1,36 @@
+[
+ {kernel, [{start_pg2, true}]},
+ {sasl, [
+ {sasl_error_logger, false}
+ ]
+ },
+
+ {deploy,[
+ {apps,[
+ {deploy,
+ %% ssh config of app servers.
+ {app_config, [
+ {test_143, [
+ {ssh, "your server ip"},
+ {port, 22},
+ {user, "user name"},
+ {password, "your password"}
+ ]},
+ {code_173, [
+ {ssh, "your server ip"},
+ {port, 22},
+ {user, "user name"},
+ {password, "your password"}
+ ]}
+ ],
+ %% beam directory of app.
+ "/home/zhaoxu-b/",
+ "/data/deploy/bin/deploy restart",
+ 'cookie_deploy_2013_by_denofiend',
+ ['deploy01@127.0.0.1', 'deploy02@127.0.0.1', 'deploy03@127.0.0.1']
+ }
+ }
+ ]}
+ ]
+ }
+].
diff --git a/rel/files/deploy b/rel/files/deploy
new file mode 100644
index 0000000..ec6d7ff
--- /dev/null
+++ b/rel/files/deploy
@@ -0,0 +1,155 @@
+#!/bin/bash
+# -*- tab-width:4;indent-tabs-mode:nil -*-
+# ex: ts=4 sw=4 et
+
+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_BASE_DIR/
+RUNNER_USER=
+
+# Make sure this script is running as the appropriate user
+if [ ! -z "$RUNNER_USER" ] && [ `whoami` != "$RUNNER_USER" ]; then
+ exec sudo -u $RUNNER_USER -i $0 $@
+fi
+
+# Make sure CWD is set to runner base dir
+cd $RUNNER_BASE_DIR
+
+# Make sure log directory exists
+mkdir -p $RUNNER_LOG_DIR
+
+# Extract the target node name from node.args
+NAME_ARG=`grep -e '-[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
+
+# Extract the target cookie
+COOKIE_ARG=`grep -e '-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
+
+# Setup command to control the node
+NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG"
+
+# Check the first argument for instructions
+case "$1" in
+ start)
+ # Make sure there is not already a node running
+ RES=`$NODETOOL ping`
+ if [ "$RES" = "pong" ]; then
+ echo "Node is already running!"
+ exit 1
+ fi
+ HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT start"
+ export HEART_COMMAND
+ mkdir -p $PIPE_DIR
+ # Note the trailing slash on $PIPE_DIR/
+ $ERTS_PATH/run_erl -daemon $PIPE_DIR/ $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT console" 2>&1
+ ;;
+
+ stop)
+ # Wait for the node to completely stop...
+ case `uname -s` in
+ Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD)
+ # PID COMMAND
+ PID=`ps ax -o pid= -o command=|\
+ grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'`
+ ;;
+ SunOS)
+ # PID COMMAND
+ PID=`ps -ef -o pid= -o args=|\
+ grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $1}'`
+ ;;
+ CYGWIN*)
+ # UID PID PPID TTY STIME COMMAND
+ PID=`ps -efW|grep "$RUNNER_BASE_DIR/.*/[b]eam"|awk '{print $2}'`
+ ;;
+ esac
+ $NODETOOL stop
+ while `kill -0 $PID 2>/dev/null`;
+ do
+ sleep 1
+ done
+ ;;
+
+ restart)
+ ## Restart the VM without exiting the process
+ $NODETOOL restart
+ ;;
+
+ reboot)
+ ## Restart the VM completely (uses heart to restart it)
+ $NODETOOL reboot
+ ;;
+
+ ping)
+ ## See if the VM is alive
+ $NODETOOL ping
+ ;;
+
+ attach)
+ # Make sure a node IS running
+ RES=`$NODETOOL ping`
+ if [ "$RES" != "pong" ]; then
+ echo "Node is not running!"
+ exit 1
+ fi
+
+ shift
+ $ERTS_PATH/to_erl $PIPE_DIR
+ ;;
+
+ console|console_clean)
+ # .boot file typically just $SCRIPT (ie, the app name)
+ # however, for debugging, sometimes start_clean.boot is useful:
+ case "$1" in
+ console) BOOTFILE=$SCRIPT ;;
+ console_clean) BOOTFILE=start_clean ;;
+ esac
+ # Setup beam-required vars
+ ROOTDIR=$RUNNER_BASE_DIR
+ BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
+ EMU=beam
+ PROGNAME=`echo $0 | sed 's/.*\\///'`
+ CMD="$BINDIR/erlexec -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -embedded -config $RUNNER_ETC_DIR/app.config -args_file $RUNNER_ETC_DIR/vm.args -- ${1+"$@"}"
+ export EMU
+ export ROOTDIR
+ export BINDIR
+ export PROGNAME
+
+ # Dump environment info for logging purposes
+ echo "Exec: $CMD"
+ echo "Root: $ROOTDIR"
+
+ # Log the startup
+ logger -t "$SCRIPT[$$]" "Starting up"
+
+ # Start the VM
+ exec $CMD
+ ;;
+
+ *)
+ echo "Usage: $SCRIPT {start|stop|restart|reboot|ping|console|attach}"
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/rel/files/env.sh b/rel/files/env.sh
new file mode 100644
index 0000000..28a6799
--- /dev/null
+++ b/rel/files/env.sh
@@ -0,0 +1,229 @@
+#!/bin/sh
+# -*- tab-width:4;indent-tabs-mode:nil -*-
+# ex: ts=4 sw=4 et
+
+# installed by node_package (github.com/basho/node_package)
+
+# /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_LOG_DIR={{runner_log_dir}}
+RUNNER_LIB_DIR={{runner_lib_dir}}
+RUNNER_PATCH_DIR={{runner_patch_dir}}
+PIPE_DIR={{pipe_dir}}
+RUNNER_USER={{runner_user}}
+APP_VERSION={{app_version}}
+
+# Variables needed to support creation of .pid files
+# PID directory and pid file name of this app
+# ex: /var/run/riak & /var/run/riak/riak.pid
+RUN_DIR="/var/run" # for now hard coded unless we find a platform that differs
+PID_DIR=$RUN_DIR/$RUNNER_SCRIPT
+PID_FILE=$PID_DIR/$RUNNER_SCRIPT.pid
+
+# Threshold where users will be warned of low ulimit file settings
+# default it if it is not set
+ULIMIT_WARN={{runner_ulimit_warn}}
+if [ -z "$ULIMIT_WARN" ]; then
+ ULIMIT_WARN=4096
+fi
+
+# Registered process to wait for to consider start a success
+WAIT_FOR_PROCESS={{runner_wait_process}}
+
+WHOAMI=$(whoami)
+
+# Echo to stderr on errors
+echoerr() { echo "$@" 1>&2; }
+
+# Extract the target node name from node.args
+NAME_ARG=`egrep '^\-s?name' $RUNNER_ETC_DIR/vm.args`
+if [ -z "$NAME_ARG" ]; then
+ echoerr "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
+ echoerr "vm.args needs to have a -setcookie parameter."
+ exit 1
+fi
+
+# 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
+
+# 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"
+
+# Ping node without stealing stdin
+ping_node() {
+ $NODETOOL ping < /dev/null
+}
+
+# Attempts to create a pid directory like /var/run/APPNAME and then
+# changes the permissions on that directory so the $RUNNER_USER can
+# read/write/delete .pid files during startup/shutdown
+create_pid_dir() {
+ # Validate RUNNER_USER is set and they have permissions to write to /var/run
+ # Don't continue if we've already sudo'd to RUNNER_USER
+ if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then
+ if [ -w $RUN_DIR ]; then
+ mkdir -p $PID_DIR
+ ES=$?
+ if [ "$ES" -ne 0 ]; then
+ return 1
+ else
+ # Change permissions on $PID_DIR
+ chown $RUNNER_USER $PID_DIR
+ ES=$?
+ if [ "$ES" -ne 0 ]; then
+ return 1
+ else
+ return 0
+ fi
+ fi
+ else
+ # If we don't have permissions, fail
+ return 1
+ fi
+ fi
+
+ # If RUNNER_USER is not set this is probably a test setup (devrel) and does
+ # not need a .pid file, so do not return error
+ return 0
+}
+
+# Attempt to create a pid file for the process
+# This function assumes the process is already up and running and can
+# respond to a getpid call. It also assumes that two processes
+# with the same name will not be run on the machine
+# Do not print any error messages as failure to create a pid file because
+# pid files are strictly optional
+# This function should really only be called in a "start" function
+# you have been warned
+create_pid_file() {
+ # Validate a pid directory even exists
+ if [ -w $PID_DIR ]; then
+ # Grab the proper pid from getpid
+ get_pid
+ ES=$?
+ if [ "$ES" -ne 0 ]; then
+ return $ES
+ else
+ # Remove pid file if it already exists since we do not
+ # plan for multiple identical runners on a single machine
+ rm -f $PID_FILE
+ echo $PID > $PID_FILE
+ return 0
+ fi
+ else
+ return 1
+ fi
+}
+
+# Function to su into correct user
+check_user() {
+ # Validate that the user running the script is the owner of the
+ # RUN_DIR.
+ if ([ "$RUNNER_USER" ] && [ "x$WHOAMI" != "x$RUNNER_USER" ]); then
+ type sudo > /dev/null 2>&1
+ if [ "$?" -ne 0 ]; then
+ echoerr "sudo doesn't appear to be installed and your EUID isn't $RUNNER_USER" 1>&2
+ exit 1
+ fi
+ exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@
+ fi
+}
+
+# Function to validate the node is down
+node_down_check() {
+ MUTE=`ping_node 2> /dev/null`
+ if [ "$?" -eq 0 ]; then
+ echoerr "Node is already running!"
+ exit 1
+ fi
+}
+
+# Function to validate the node is up
+node_up_check() {
+ MUTE=`ping_node 2> /dev/null`
+ if [ "$?" -ne 0 ]; then
+ echoerr "Node is not running!"
+ exit 1
+ fi
+}
+
+# Function to check if the config file is valid
+check_config() {
+ MUTE=`$NODETOOL_LITE chkconfig $RUNNER_ETC_DIR/app.config`
+ if [ "$?" -ne 0 ]; then
+ echoerr "Error reading $RUNNER_ETC_DIR/app.config"
+ exit 1
+ fi
+ echo "config is OK"
+}
+
+# Function to check if ulimit is properly set
+check_ulimit() {
+
+ # don't fail if this is unset
+ if [ ! -z "$ULIMIT_WARN" ]; then
+ ULIMIT_F=`ulimit -n`
+ if [ "$ULIMIT_F" -lt $ULIMIT_WARN ]; then
+ echo "!!!!"
+ echo "!!!! WARNING: ulimit -n is ${ULIMIT_F}; ${ULIMIT_WARN} is the recommended minimum."
+ echo "!!!!"
+ fi
+ fi
+}
+
+# Set the PID global variable, return 1 on error
+get_pid() {
+ PID=`$NODETOOL getpid < /dev/null`
+ if [ "$?" -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
+}
diff --git a/rel/files/erl b/rel/files/erl
new file mode 100644
index 0000000..e500626
--- /dev/null
+++ b/rel/files/erl
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+## This script replaces the default "erl" in erts-VSN/bin. This is necessary
+## as escript depends on erl and in turn, erl depends on having access to a
+## bootscript (start.boot). Note that this script is ONLY invoked as a side-effect
+## of running escript -- the embedded node bypasses erl and uses erlexec directly
+## (as it should).
+##
+## Note that this script makes the assumption that there is a start_clean.boot
+## file available in $ROOTDIR/release/VSN.
+
+# Determine the abspath of where this script is executing from.
+ERTS_BIN_DIR=$(cd ${0%/*} && pwd)
+
+# Now determine the root directory -- this script runs from erts-VSN/bin,
+# so we simply need to strip off two dirs from the end of the ERTS_BIN_DIR
+# path.
+ROOTDIR=${ERTS_BIN_DIR%/*/*}
+
+# Parse out release and erts info
+START_ERL=`cat $ROOTDIR/releases/start_erl.data`
+ERTS_VSN=${START_ERL% *}
+APP_VSN=${START_ERL#* }
+
+BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin
+EMU=beam
+PROGNAME=`echo $0 | sed 's/.*\\///'`
+CMD="$BINDIR/erlexec"
+export EMU
+export ROOTDIR
+export BINDIR
+export PROGNAME
+
+exec $CMD -boot $ROOTDIR/releases/$APP_VSN/start_clean ${1+"$@"}
\ No newline at end of file
diff --git a/rel/files/nodetool b/rel/files/nodetool
new file mode 100644
index 0000000..eb08fa4
--- /dev/null
+++ b/rel/files/nodetool
@@ -0,0 +1,138 @@
+%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ft=erlang ts=4 sw=4 et
+%% -------------------------------------------------------------------
+%%
+%% nodetool: Helper Script for interacting with live nodes
+%%
+%% -------------------------------------------------------------------
+
+main(Args) ->
+ ok = start_epmd(),
+ %% Extract the args
+ {RestArgs, TargetNode} = process_args(Args, [], undefined),
+
+ %% See if the node is currently running -- if it's not, we'll bail
+ 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]),
+ halt(1)
+ end,
+
+ case RestArgs of
+ ["ping"] ->
+ %% If we got this far, the node already responsed to a ping, so just dump
+ %% a "pong"
+ io:format("pong\n");
+ ["stop"] ->
+ io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]);
+ ["restart"] ->
+ io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]);
+ ["reboot"] ->
+ io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]);
+ ["rpc", Module, Function | RpcArgs] ->
+ case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
+ [RpcArgs], 60000) of
+ ok ->
+ ok;
+ {badrpc, Reason} ->
+ io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
+ halt(1);
+ _ ->
+ halt(1)
+ end;
+ ["rpcterms", Module, Function, ArgsAsString] ->
+ case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function),
+ consult(ArgsAsString), 60000) of
+ {badrpc, Reason} ->
+ io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]),
+ halt(1);
+ Other ->
+ io:format("~p\n", [Other])
+ end;
+ Other ->
+ io:format("Other: ~p\n", [Other]),
+ io:format("Usage: nodetool {ping|stop|restart|reboot}\n")
+ end,
+ net_kernel:stop().
+
+process_args([], Acc, TargetNode) ->
+ {lists:reverse(Acc), TargetNode};
+process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) ->
+ erlang:set_cookie(node(), list_to_atom(Cookie)),
+ process_args(Rest, Acc, TargetNode);
+process_args(["-name", TargetName | Rest], Acc, _) ->
+ ThisNode = append_node_suffix(TargetName, "_maint_"),
+ {ok, _} = net_kernel:start([ThisNode, longnames]),
+ process_args(Rest, Acc, nodename(TargetName));
+process_args(["-sname", TargetName | Rest], Acc, _) ->
+ ThisNode = append_node_suffix(TargetName, "_maint_"),
+ {ok, _} = net_kernel:start([ThisNode, shortnames]),
+ process_args(Rest, Acc, nodename(TargetName));
+process_args([Arg | Rest], Acc, Opts) ->
+ process_args(Rest, [Arg | Acc], Opts).
+
+
+start_epmd() ->
+ [] = os:cmd(epmd_path() ++ " -daemon"),
+ ok.
+
+epmd_path() ->
+ ErtsBinDir = filename:dirname(escript:script_name()),
+ Name = "epmd",
+ case os:find_executable(Name, ErtsBinDir) of
+ false ->
+ case os:find_executable(Name) of
+ false ->
+ io:format("Could not find epmd.~n"),
+ halt(1);
+ GlobalEpmd ->
+ GlobalEpmd
+ end;
+ Epmd ->
+ Epmd
+ end.
+
+
+nodename(Name) ->
+ case string:tokens(Name, "@") of
+ [_Node, _Host] ->
+ list_to_atom(Name);
+ [Node] ->
+ [_, Host] = string:tokens(atom_to_list(node()), "@"),
+ list_to_atom(lists:concat([Node, "@", Host]))
+ end.
+
+append_node_suffix(Name, Suffix) ->
+ case string:tokens(Name, "@") of
+ [Node, Host] ->
+ list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host]));
+ [Node] ->
+ 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
+%%
+consult(Str) when is_list(Str) ->
+ consult([], Str, []);
+consult(Bin) when is_binary(Bin)->
+ consult([], binary_to_list(Bin), []).
+
+consult(Cont, Str, Acc) ->
+ case erl_scan:tokens(Cont, Str, 0) of
+ {done, Result, Remaining} ->
+ case Result of
+ {ok, Tokens, _} ->
+ {ok, Term} = erl_parse:parse_term(Tokens),
+ consult([], Remaining, [Term | Acc]);
+ {eof, _Other} ->
+ lists:reverse(Acc);
+ {error, Info, _} ->
+ {error, Info}
+ end;
+ {more, Cont1} ->
+ consult(Cont1, eof, Acc)
+ end.
diff --git a/rel/files/vm.args b/rel/files/vm.args
new file mode 100644
index 0000000..c3f7193
--- /dev/null
+++ b/rel/files/vm.args
@@ -0,0 +1,29 @@
+
+## Name of the node
+-name deploy@127.0.0.1
+
+## Cookie for distributed erlang
+-setcookie cookie_deploy_2013_by_denofiend
+
+## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
+## (Disabled by default..use with caution!)
+##-heart
+
+## Enable kernel poll and a few async threads
++K true
+
++A 64
+
++P 250000
+
++zdbbl 32768
+
++S 12:12
+## Increase number of concurrent ports/sockets
+-env ERL_MAX_PORTS 64000
+-env ERL_MAX_ETS_TABLES 256000
+
+## Tweak GC to run more often
+-env ERL_FULLSWEEP_AFTER 0
+
+-env ERL_CRASH_DUMP_SECONDS 15
diff --git a/rel/reltool.config b/rel/reltool.config
new file mode 100644
index 0000000..0a1e19a
--- /dev/null
+++ b/rel/reltool.config
@@ -0,0 +1,35 @@
+{sys, [
+ {lib_dirs, ["../../"]},
+ {rel, "deploy", "1.0.0",
+ [
+ %% deps system application
+ kernel,
+ stdlib,
+ deploy
+ ]},
+ {app_file, strip},
+ {rel, "start_clean", "",
+ [
+ kernel,
+ stdlib
+ ]},
+ {boot_rel, "deploy"},
+ {profile, embedded},
+ {excl_archive_filters, [".*"]}, %% Do not archive built libs
+ {excl_sys_filters, ["^bin/.*",
+ "^erts.*/bin/(dialyzer|typer)"]},
+ {app, deploy, [{incl_cond, include}]}
+]}.
+
+{target_dir, "deploy"}.
+
+{overlay_vars, "vars.config"}.
+
+{overlay, [
+ {copy, "files/erl", "{{erts_vsn}}/bin/erl"},
+ {copy, "files/nodetool", "{{erts_vsn}}/bin/nodetool"},
+ {copy, "files/deploy", "bin/deploy"},
+ {copy, "files/app.config", "etc/app.config"},
+ {copy, "files/vm.args", "etc/vm.args"},
+ {template, "files/env.sh", "lib/env.sh"}
+]}.
diff --git a/rel/vars.config b/rel/vars.config
new file mode 100644
index 0000000..62cc56d
--- /dev/null
+++ b/rel/vars.config
@@ -0,0 +1,31 @@
+%% -*- 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"}.
+
+%%
+%% etc/app.config
+%%
+{ring_state_dir, "{{platform_data_dir}}/ring"}.
+{bitcask_data_root, "{{platform_data_dir}}/bitcask"}.
+{leveldb_data_root, "{{platform_data_dir}}/leveldb"}.
+{sasl_error_log, "{{platform_log_dir}}/sasl-error.log"}.
+{sasl_log_dir, "{{platform_log_dir}}/sasl"}.
+
+
+%%
+%% bin/riak
+%%
+{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"}.
+{runner_lib_dir, "$RUNNER_BASE_DIR/lib"}.
+{runner_patch_dir, "$RUNNER_BASE_DIR/lib/basho-patches"}.
+{pipe_dir, "/tmp/$RUNNER_BASE_DIR/"}.
+{runner_user, ""}.
diff --git a/src/deploy.app.src b/src/deploy.app.src
new file mode 100644
index 0000000..3886f4d
--- /dev/null
+++ b/src/deploy.app.src
@@ -0,0 +1,20 @@
+%%%-------------------------------------------------------------------
+%%% @author zhaoxu-b
+%%% @copyright (C) 2013,
+%%% @doc
+%%%
+%%% @end
+%%% Created : 31. 十月 2013 上午10:31
+%%%-------------------------------------------------------------------
+{application, deploy, [
+ {description, ""},
+ {vsn, "1.0.0"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, {deploy, []}},
+ {env, [
+ ]}
+]}.
diff --git a/src/deploy.erl b/src/deploy.erl
new file mode 100644
index 0000000..b2fe178
--- /dev/null
+++ b/src/deploy.erl
@@ -0,0 +1,255 @@
+%%%-------------------------------------------------------------------
+%%% @author zhaoxu-b
+%%% @copyright (C) 2013,
+%%% @doc
+%%%
+%%% @end
+%%% Created : 30. 十月 2013 下午4:21
+%%%-------------------------------------------------------------------
+-module(deploy).
+-author("zhaoxu-b").
+
+-behaviour(application).
+
+-export([
+ start/2,
+ stop/1, deploy_file/2, restart_server/1, deploy_dir/2, hot_upgrade/2
+]).
+
+
+-record(app_config, {
+ server_list,
+ app_dir,
+ restart_command,
+ cookie,
+ nodes
+}).
+
+%% --------------------------------------------------------------------
+%% Function: start_ssh_config/0
+%% Description: start ct_run, ct_config, ct_util
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%% --------------------------------------------------------------------
+start_ssh_config() ->
+ io:format("start_ssh_config"),
+ ct_run:install([]),
+ ct_config:start(interactive),
+ ct_util:start(),
+
+ Configs = case application:get_env(deploy, apps) of
+ {ok, AppConfigs} ->
+ AppConfigs;
+ _ ->
+ []
+ end,
+ io:format("Configs:~p~n", [Configs]),
+
+ lists:foreach(
+ fun(Conf) ->
+ io:format("Conf:~p~n", [Conf]),
+ {_App, #app_config{server_list = ServerList}} = Conf,
+ lists:foreach(
+ fun(OneConfig) ->
+ io:format("Name:~p, OneConfig:~p~n", ['_UNDEF', [OneConfig]]),
+ ct_config:set_default_config('_UNDEF', [OneConfig], undefined)
+ end, ServerList)
+ end, Configs).
+
+
+start(_Type, []) ->
+ start_ssh_config(),
+ deploy_sup:start_link().
+
+%% @spec stop(State::term()) -> any()
+%%
+%% @doc This is callback after application shut down.
+%% @private
+stop(State) ->
+ ct_util:stop(State),
+ ok.
+
+
+%% --------------------------------------------------------------------
+%% Function: get_config/1
+%% Description: get app config from application env.
+%% Returns: {ok, Config}
+%% --------------------------------------------------------------------
+get_config(App) ->
+ case application:get_env(deploy, apps) of
+ {ok, Configs} ->
+ case lists:keyfind(App, 1, Configs) of
+ false ->
+ {error, lists:connect([App, " is undefined"])};
+ Config ->
+ {ok, Config}
+ end;
+ _ ->
+ {error, "apps is undfined"}
+ end.
+
+%% --------------------------------------------------------------------
+%% Function: send_dir/4
+%% Description: scp directory.
+%% Returns: ok
+%% --------------------------------------------------------------------
+send_dir(_AppDir, _CH, _Dir, []) ->
+ ok;
+
+send_dir(AppDir, CH, Dir, [File | T]) ->
+ FullName = filename:join([Dir, File]),
+ case filelib:is_file(FullName) of
+ true ->
+ %% sync file to server
+ {ok, FileData} = file:read_file(FullName),
+ ct_ssh:write_file(CH, lists:concat([AppDir, filename:basename(FullName)]), FileData);
+ false ->
+ send_dir(AppDir, CH, FullName, file:list_dir(FullName))
+ end,
+ send_dir(AppDir, CH, Dir, T).
+
+%% --------------------------------------------------------------------
+%% Function:deploy_dir/2
+%% Description: deploy directory to remote servers for app.
+%% Returns: ok
+%% --------------------------------------------------------------------
+deploy_dir(App, Dir) ->
+ io:format("deploy dir ~p servers, Directory:~p~n", [App, Dir]),
+
+ try
+ case get_config(App) of
+ {ok, {App, Config}} ->
+ #app_config{server_list = ServerList, app_dir = AppDir} = Config,
+ {ok, FileNames} = file:list_dir(Dir),
+
+ lists:foreach(
+ fun(ServerConfig) ->
+ case ServerConfig of
+ {Name, _} ->
+ {ok, CH} = ct_ssh:connect(Name, ssh),
+
+ send_dir(AppDir, CH, Dir, FileNames),
+
+ ct_ssh:disconnect(CH);
+ _ ->
+ throw({error, "ssh config error"})
+ end
+ end, ServerList),
+ ok;
+ _ ->
+ io:format("~p is undefined App in deploy.app file.~n", [App]),
+ throw({error, "app is undefined"})
+ end
+ catch
+ Any ->
+ Any
+ end.
+
+%% --------------------------------------------------------------------
+%% Function:deploy_file/2
+%% Description: deploy file to remote servers for app.
+%% Returns: ok
+%% --------------------------------------------------------------------
+deploy_file(App, File) ->
+ io:format("deploy file ~p servers, file:~p~n", [App, File]),
+ try
+ case get_config(App) of
+ {ok, {App, Config}} ->
+ #app_config{server_list = ServerConfigList, app_dir = AppDir} = Config,
+ %% sync file to server
+ case file:read_file(File) of
+ {ok, FileData} ->
+ lists:foreach(
+ fun(ServerConfig) ->
+ case ServerConfig of
+ {Name, _} ->
+ %%io:format("Name:~p, AppDir:~p, RemoteRelPath:~p~n", [Name, AppDir, lists:concat([AppDir, filename:basename(File)])]),
+ {ok, CH} = ct_ssh:connect(Name, sftp),
+ ct_ssh:write_file(CH, lists:concat([AppDir, filename:basename(File)]), FileData),
+ ct_ssh:disconnect(CH);
+ _ ->
+ throw({error, "ssh config error"})
+ end
+ end, ServerConfigList);
+ _ ->
+ throw({error, "file is not exists."})
+ end,
+ ok;
+ Error ->
+ io:format("err:~p.~n", [Error]),
+ Error
+ end
+ catch
+ Any ->
+ Any
+ end.
+
+%% --------------------------------------------------------------------
+%% Function:restart_server/1
+%% Description: restart all servers of App.
+%% Returns: ok
+%% --------------------------------------------------------------------
+restart_server(App) ->
+ io:format("restart ~p servers~n", [App]),
+ try
+ case get_config(App) of
+ {ok, {App, Config}} ->
+ #app_config{server_list = ServerList, restart_command = RestartCommand} = Config,
+ lists:foreach(
+ fun(ServerConfig) ->
+ case ServerConfig of
+ {Name, _} ->
+ {ok, CH1} = ct_ssh:connect(Name, ssh),
+ ct_ssh:exec(CH1, RestartCommand),
+ ct_ssh:disconnect(CH1);
+ _ ->
+ throw({error, "ssh config error"})
+ end
+ end, ServerList),
+ ok;
+ Error ->
+ io:format("err:~p.~n", [Error]),
+ Error
+ end
+ catch
+ Any ->
+ Any
+ end.
+
+%% --------------------------------------------------------------------
+%% Function:hot_upgrade/2
+%% Description: hot upgrade one Module in all servers of App.
+%% Returns: ok
+%% --------------------------------------------------------------------
+hot_upgrade(App, Module) ->
+ io:format("hot upgrade ~p servers, Module:~p~n", [App, Module]),
+ try
+ case get_config(App) of
+ {ok, {App, Config}} ->
+ #app_config{cookie = Cookie, nodes = Nodes} = Config,
+
+ %% set cookie
+ erlang:set_cookie(node(), Cookie),
+ %% ping other nodes.
+ lists:foreach(fun(ServerNode) -> net_adm:ping(ServerNode) end, Nodes),
+
+ io:format("server nodes:~p~n", [nodes()]),
+
+ %% hot upgrade the Module
+ {Mod, Bin, File} = code:get_object_code(Module),
+ {ResL, BadNodes} = rpc:multicall(code, load_binary, [Mod, File, Bin]),
+ io:format("Res:~p, BadNodes:~p~n", [ResL, BadNodes]),
+ ok;
+ Error ->
+ io:format("err:~p.~n", [Error]),
+ Error
+ end
+ catch
+ Any ->
+ Any
+ end.
+
diff --git a/src/deploy_sup.erl b/src/deploy_sup.erl
new file mode 100644
index 0000000..ff7e66f
--- /dev/null
+++ b/src/deploy_sup.erl
@@ -0,0 +1,64 @@
+%%%-------------------------------------------------------------------
+%%% @author zhaoxu-b
+%%% @copyright (C) 2013,
+%%% @doc
+%%%
+%%% @end
+%%% Created : 01. 十一月 2013 下午3:56
+%%%-------------------------------------------------------------------
+-module(deploy_sup).
+-author("zhaoxu-b").
+
+-behaviour(supervisor).
+
+%% API
+-export([start_link/0]).
+
+%% Supervisor callbacks
+-export([init/1]).
+
+-define(SERVER, ?MODULE).
+
+%%%===================================================================
+%%% API functions
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the supervisor
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link() ->
+ supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+
+%%%===================================================================
+%%% Supervisor callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Whenever a supervisor is started using supervisor:start_link/[2,3],
+%% this function is called by the new process to find out about
+%% restart strategy, maximum restart frequency and child
+%% specifications.
+%%
+%% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} |
+%% ignore |
+%% {error, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([]) ->
+ RestartStrategy = one_for_one,
+ MaxRestarts = 1000,
+ MaxSecondsBetweenRestarts = 3600,
+
+ SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
+
+ {ok, {SupFlags, []}}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================