diff --git a/.gitignore b/.gitignore index 49cd4e3..0ae69b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,14 @@ *.beam .rebar erl_crash.dump ebin deps db log .*.swp Mnesia* test.spec cover.spec test_logs -rel/ _build rebar.lock diff --git a/rel/files/erl b/rel/files/erl new file mode 100755 index 0000000..f4c63af --- /dev/null +++ b/rel/files/erl @@ -0,0 +1,44 @@ +#!/bin/sh + +# /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 + exec /usr/bin/ksh $0 "$@" +fi + +# clear it so if we invoke other scripts, they run as ksh as well +unset POSIX_SHELL + +## 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 -P) + +# 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+"$@"} diff --git a/rel/files/install_upgrade.escript b/rel/files/install_upgrade.escript new file mode 100644 index 0000000..253b7fa --- /dev/null +++ b/rel/files/install_upgrade.escript @@ -0,0 +1,55 @@ +#!/usr/bin/env escript +%%! -noshell -noinput +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et +%% This file is left for backward-compatibility. +%% You, probably, shouldn't include it to new projects. + + +main([NodeName, Cookie, ReleasePackage]) -> + io:format("WARNING: 'install_upgrade.escript' is deprecated! " + "Use 'nodetool upgrade' instead.~n"), + NodeRoot = filename:dirname(filename:dirname(escript:script_name())), + NodeTool = which_nodetool(NodeRoot), + process_flag(trap_exit, true), + Port = erlang:open_port( + {spawn_executable, NodeTool}, + [{args, ["-sname", NodeName, + "-setcookie", Cookie, + "upgrade", ReleasePackage]}, + binary, exit_status, use_stdio, stderr_to_stdout, hide]), + port_loop(Port); +main(_) -> + halt(1). + + +which_nodetool(NodeRoot) -> + %% ${RELEASE_ROOT}/ + %% bin/install_upgrade.escript + %% bin/nodetool ? + %% erts-/bin/nodetool ? + %% releases//nodetool ? + %% releases/start_erl.data + {ok, Content} = file:read_file(filename:join([NodeRoot, "releases", "start_erl.data"])), + [ErtsVsn, AppVsn] = binary:split(Content, <<" ">>), + Probes = [ + filename:join([NodeRoot, "bin", "nodetool"]), + filename:join([NodeRoot, <<"erts-", ErtsVsn/binary>>, "bin", "nodetool"]), + filename:join([NodeRoot, "releases", AppVsn, "bin", "nodetool"]) + ], + case lists:dropwhile(fun(Path) -> not filelib:is_regular(Path) end, Probes) of + [] -> + io:format("ERROR: can't find 'nodetool' in ~p.~n", [Probes]), + halt(2); + [Path | _] -> + Path + end. + +port_loop(Port) -> + receive + {Port, {data, Data}} -> + io:put_chars(Data), + port_loop(Port); + {Port, {exit_status, Status}} -> + halt(Status) + end. diff --git a/rel/files/kolab_guam b/rel/files/kolab_guam new file mode 100755 index 0000000..814b112 --- /dev/null +++ b/rel/files/kolab_guam @@ -0,0 +1,433 @@ +#!/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' on old Solaris systems, add /usr/ucb to path + # (New Solaris systems do not have 'whoami') + PATH=/usr/ucb:$PATH + export PATH + exec /usr/bin/ksh $0 "$@" +fi + +# clear it so if we invoke other scripts, they run as ksh +unset POSIX_SHELL + +RUNNER_SCRIPT_DIR=$(cd ${0%/*} && pwd -P) +RUNNER_SCRIPT=${0##*/} + +CALLER_DIR=$PWD + +RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*} +RUNNER_ETC_DIR=$RUNNER_BASE_DIR/etc +# Note the trailing slash on $PIPE_DIR/ +PIPE_DIR=/tmp/$RUNNER_BASE_DIR/ +RUNNER_USER= + +# Make sure this script is running as the appropriate user +if [ "$RUNNER_USER" ]; then + WHOAMI=$(id -un 2>/dev/null || whoami 2>/dev/null) + if [ $? -ne 0 ]; then + echo "Could not determine user name." + exit 1 + fi + if [ "x$WHOAMI" != "x$RUNNER_USER" ]; then + # The 'su' command is more portable, but can't be configured as 'sudo' + # can to allow non-interactive calls from non-root users. + type sudo > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "Attempting to restart script through sudo -H -u $RUNNER_USER" >&2 + exec sudo -H -u $RUNNER_USER -i $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ + else + if [ "x$WHOAMI" != "xroot" ]; then + echo "Only root can run $RUNNER_SCRIPT as $RUNNER_USER without requiring a password." + exit 1 + else + echo "Attempting to restart script through su $RUNNER_USER" >&2 + exec su $RUNNER_USER -c $RUNNER_SCRIPT_DIR/$RUNNER_SCRIPT $@ + fi + fi + fi +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#* } + +# Use $CWD/vm.args if exists, otherwise releases/APP_VSN/vm.args, or +# else etc/vm.args +if [ -e "$CALLER_DIR/vm.args" ]; then + VMARGS_PATH=$CALLER_DIR/vm.args + USE_DIR=$CALLER_DIR +else + USE_DIR=$RUNNER_BASE_DIR + if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" ]; then + VMARGS_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/vm.args" + else + VMARGS_PATH="$RUNNER_ETC_DIR/vm.args" + fi +fi + +RUNNER_LOG_DIR=$USE_DIR/log +# Make sure log directory exists +mkdir -p $RUNNER_LOG_DIR + +# Use releases/VSN/sys.config if it exists otherwise use etc/app.config +if [ -e "$USE_DIR/sys.config" ]; then + CONFIG_PATH="$USE_DIR/sys.config" +else + if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" ]; then + CONFIG_PATH="$RUNNER_BASE_DIR/releases/$APP_VSN/sys.config" + else + CONFIG_PATH="$RUNNER_ETC_DIR/app.config" + fi +fi + +# Extract the target node name from node.args +NAME_ARG=`egrep '^\-s?name' $VMARGS_PATH` +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 +REMSH_TYPE=`echo $NAME_ARG | awk '{print $1}'` +REMSH_NAME=`echo $NAME_ARG | awk '{print $2}'` + +# Test if REMSH_NAME contains a @ and set REMSH_HOSTNAME_PART +# and REMSH_NAME_PART according REMSH_TYPE +MAYBE_FQDN_HOSTNAME=`hostname` +HOSTNAME=`echo $MAYBE_FQDN_HOSTNAME | awk -F. '{print $1}'` + +REMSH_HOSTNAME_PART="$MAYBE_FQDN_HOSTNAME" +case "$REMSH_NAME" in + *@*) + REMSH_HOSTNAME_PART=`echo $REMSH_NAME | awk -F@ '{print $2}'` + REMSH_NAME_PART=`echo $REMSH_NAME | awk -F@ '{print $1}'` + ;; + *) + REMSH_NAME_PART="$REMSH_NAME" + if [ "$REMSH_TYPE" = "-sname" ]; then + REMSH_HOSTNAME_PART="$HOSTNAME" + else + # -name type, check if `hostname` is fqdn + if [ "$MAYBE_FQDN_HOSTNAME" = "$HOSTNAME" ]; then + echo "Hostname must be a fqdn domain name when node is configured with long names" + echo "and the full node name isn't configured in vm.args" + exit 1 + fi + fi + ;; +esac + +# Note the `date +%s`, used to allow multiple remsh to the same node +# transparently +REMSH_NAME_ARG="$REMSH_TYPE remsh`date +%s`@$REMSH_HOSTNAME_PART" +REMSH_REMSH_ARG="-remsh $REMSH_NAME_PART@$REMSH_HOSTNAME_PART" + +# Extract the target cookie +COOKIE_ARG=`grep '^\-setcookie' $VMARGS_PATH` +if [ -z "$COOKIE_ARG" ]; then + echo "vm.args needs to have a -setcookie parameter." + exit 1 +fi + +# Make sure CWD is set to the right dir +cd $USE_DIR + +# Make sure log directory exists +mkdir -p $USE_DIR/log + +RUNNER_SCRIPT_DATA= +if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/runner_script.data" ]; then + RUNNER_SCRIPT_DATA=`cat $RUNNER_BASE_DIR/releases/$APP_VSN/runner_script.data` +fi + +if [ -z "$RUNNER_SCRIPT_DATA" ]; then + ROOTDIR=$RUNNER_BASE_DIR + ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin + if [ -e "$RUNNER_BASE_DIR/releases/$APP_VSN/nodetool" ]; then + NODETOOL="$ERTS_PATH/escript $RUNNER_BASE_DIR/releases/$APP_VSN/nodetool $NAME_ARG $COOKIE_ARG" + else + NODETOOL="$ERTS_PATH/escript $ERTS_PATH/nodetool $NAME_ARG $COOKIE_ARG" + fi + SLIM_ARGS= +elif [ "$RUNNER_SCRIPT_DATA" = "slim" ]; then + # Setup system paths + SYSTEM_ERL_PATH=`which erl` + if [ ! -x "$SYSTEM_ERL_PATH" ]; then + echo "Failed to find erl. Is Erlang/OTP available in PATH?" + exit 1 + fi + SYSTEM_HOME_BIN=${SYSTEM_ERL_PATH%/*} + ROOTDIR=$SYSTEM_HOME_BIN/../lib/erlang + ERTS_PATH=$ROOTDIR/erts-$ERTS_VSN/bin + unset SYSTEM_ERL_PATH + unset SYSTEM_HOME_BIN + + LOCAL_ERTS_PATH=$RUNNER_BASE_DIR/erts-$ERTS_VSN/bin + NODETOOL="$ERTS_PATH/escript $RUNNER_BASE_DIR/releases/$APP_VSN/nodetool $NAME_ARG $COOKIE_ARG" + unset LOCAL_ERL_PATH + + # Setup additional arguments for slim release + SLIM_ARGS="-boot_var RELTOOL_EXT_LIB $RUNNER_BASE_DIR/lib -sasl releases_dir \"$RUNNER_BASE_DIR/releases\"" +else + echo "Unknown runner_script.data" + exit 1 +fi + +# Setup remote shell command to control node +REMSH="$ERTS_PATH/erl -hidden $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG" + +# 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 +} + +# Check the first argument for instructions +case "$1" in + start|start_boot) + # Make sure there is not already a node running + RES=`ping_node` + if [ "$RES" = "pong" ]; then + echo "Node is already running!" + exit 1 + fi + 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=$(printf "\'%s\' " "$@") + HEART_COMMAND="$RUNNER_BASE_DIR/bin/$SCRIPT $HEART_OPTION $RUN_PARAM" + export HEART_COMMAND + mkdir -p $PIPE_DIR + $ERTS_PATH/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR "exec $RUNNER_BASE_DIR/bin/$SCRIPT $START_OPTION $RUN_PARAM" 2>&1 + ;; + + stop) + # Wait for the node to completely stop... + 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` + do + sleep 1 + done + ;; + + restart) + ## Restart the VM without exiting the process + $NODETOOL restart + ES=$? + if [ "$ES" -ne 0 ]; then + exit $ES + fi + ;; + + reboot) + ## Restart the VM completely (uses heart to restart it) + $NODETOOL reboot + ES=$? + if [ "$ES" -ne 0 ]; then + exit $ES + fi + ;; + + ping) + ## See if the VM is alive + ping_node + ES=$? + if [ "$ES" -ne 0 ]; then + exit $ES + fi + ;; + + attach) + # Make sure a node is running + ping_node + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + exit $ES + fi + + shift + exec $ERTS_PATH/to_erl $PIPE_DIR + ;; + + eval) + # Make sure a node IS running + ping_node > /dev/null 2>&1 + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + exit $ES + fi + + shift + $NODETOOL eval "$1" + ES_EVAL=$? + if [ "$ES_EVAL" -ne 0 ]; then + exit $ES_EVAL + fi + ;; + + remote_console) + # Make sure a node is running + ping_node + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + exit $ES + fi + + shift + exec $REMSH + ;; + + upgrade) + if [ -z "$2" ]; then + echo "Missing upgrade package argument" + echo "Usage: $SCRIPT upgrade {package base name}" + echo "NOTE {package base name} MUST NOT include the .tar.gz suffix" + exit 1 + fi + + # Make sure a node IS running + ping_node + ES=$? + if [ "$ES" -ne 0 ]; then + echo "Node is not running!" + exit $ES + fi + + $NODETOOL upgrade $2 + ;; + + console|console_clean|console_boot) + # .boot file typically just $SCRIPT (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) BOOTFILE=$SCRIPT ;; + console_clean) BOOTFILE=start_clean ;; + console_boot) + shift + BOOTFILE="$1" + shift + ;; + esac + # Setup beam-required vars + BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*\\///'` + CMD="$BINDIR/erlexec $SLIM_ARGS -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -mode embedded -config $CONFIG_PATH -args_file $VMARGS_PATH" + export EMU + export ROOTDIR + export BINDIR + export PROGNAME + + # Dump environment info for logging purposes + echo "Exec: $CMD" -- ${1+"$@"} + echo "Root: $ROOTDIR" + + # Log the startup + logger -t "$SCRIPT[$$]" "Starting up" + + # Start the VM + exec $CMD -- ${1+"$@"} + ;; + + foreground) + # start up the release in the foreground for use by runit + # or other supervision services + + BOOTFILE=$SCRIPT + FOREGROUNDOPTIONS="-noinput +Bd" + + # Setup beam-required vars + BINDIR=$ROOTDIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*\///'` + CMD="$BINDIR/erlexec $SLIM_ARGS $FOREGROUNDOPTIONS -boot $RUNNER_BASE_DIR/releases/$APP_VSN/$BOOTFILE -config $CONFIG_PATH -args_file $VMARGS_PATH" + export EMU + export ROOTDIR + export BINDIR + export PROGNAME + + # Dump environment info for logging purposes + echo "Exec: $CMD" -- ${1+"$@"} + echo "Root: $ROOTDIR" + + # Start the VM + exec $CMD -- ${1+"$@"} + ;; + getpid) + # Get the PID from nodetool + get_pid + ES=$? + if [ "$ES" -ne 0 ] || [ -z $PID ]; then + exit $ES + fi + echo $PID + ;; + *) + echo "Usage: $SCRIPT {start|start_boot |foreground|stop|restart|reboot|ping|console|getpid|console_clean|console_boot |attach|eval|remote_console|upgrade}" + exit 1 + ;; +esac + +exit 0 diff --git a/rel/files/kolab_guam.cmd b/rel/files/kolab_guam.cmd new file mode 100644 index 0000000..b3a06c4 --- /dev/null +++ b/rel/files/kolab_guam.cmd @@ -0,0 +1,116 @@ +@setlocal + +@set node_name=kolab_guam + +@rem Get the absolute path to the parent directory, +@rem which is assumed to be the node root. +@for /F "delims=" %%I in ("%~dp0..") do @set node_root=%%~fI + +@rem CWD to the node root directory +@cd %node_root% + +@set releases_dir=%node_root%\releases + +@rem Parse ERTS version and release version from start_erl.data +@for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @( + @call :set_trim erts_version %%I + @call :set_trim release_version %%J +) + +@if exist "%releases_dir%\%release_version%\vm.args" ( + @set vm_args="%releases_dir%\%release_version%\vm.args" +) else ( + @set vm_args="%node_root%\etc\vm.args" +) + +@if exist "%releases_dir%\%release_version%\sys.config" ( + @set sys_config="%releases_dir%\%release_version%\sys.config" +) else ( + @set sys_config="%node_root%\etc\app.config" +) + +@set node_boot_script=%releases_dir%\%release_version%\%node_name% +@set clean_boot_script=%releases_dir%\%release_version%\start_clean + +@rem extract erlang cookie from vm.args +@for /f "usebackq tokens=1-2" %%I in (`findstr /b \-setcookie "%vm_args%"`) do @set erlang_cookie=%%J + +@set erts_bin=%node_root%\erts-%erts_version%\bin + +@set service_name=%node_name%_%release_version% + +@set erlsrv="%erts_bin%\erlsrv.exe" +@set epmd="%erts_bin%\epmd.exe" +@set escript="%erts_bin%\escript.exe" +@set werl="%erts_bin%\werl.exe" +@set nodetool="%erts_bin%\nodetool" + +@if "%1"=="usage" @goto usage +@if "%1"=="install" @goto install +@if "%1"=="uninstall" @goto uninstall +@if "%1"=="start" @goto start +@if "%1"=="stop" @goto stop +@if "%1"=="restart" @call :stop && @goto start +@if "%1"=="console" @goto console +@if "%1"=="ping" @goto ping +@if "%1"=="query" @goto query +@if "%1"=="attach" @goto attach +@if "%1"=="upgrade" @goto upgrade +@echo Unknown command: "%1" + +:usage +@echo Usage: %~n0 [install^|uninstall^|start^|stop^|restart^|console^|ping^|query^|attach^|upgrade] +@goto :EOF + +:install +@set description=Erlang node %node_name% in %node_root% +@set start_erl=%node_root%\bin\start_erl.cmd +@set args= ++ %node_name% ++ %node_root% +@%erlsrv% add %service_name% -c "%description%" -sname %node_name% -w "%node_root%" -m "%start_erl%" -args "%args%" -stopaction "init:stop()." +@goto :EOF + +:uninstall +@%erlsrv% remove %service_name% +@%epmd% -kill +@goto :EOF + +:start +@%erlsrv% start %service_name% +@goto :EOF + +:stop +@%erlsrv% stop %service_name% +@goto :EOF + +:console +@start "%node_name% console" %werl% -boot "%node_boot_script%" -config "%sys_config%" -args_file "%vm_args%" -sname %node_name% +@goto :EOF + +:ping +@%escript% %nodetool% ping -sname "%node_name%" -setcookie "%erlang_cookie%" +@exit %ERRORLEVEL% +@goto :EOF + +:query +@%erlsrv% list %service_name% +@exit %ERRORLEVEL% +@goto :EOF + +:attach +@for /f "usebackq" %%I in (`hostname`) do @set hostname=%%I +start "%node_name% attach" %werl% -boot "%clean_boot_script%" -remsh %node_name%@%hostname% -sname console -setcookie %erlang_cookie% +@goto :EOF + +:upgrade +@if "%2"=="" ( + @echo Missing upgrade package argument + @echo Usage: %~n0 upgrade {package base name} + @echo NOTE {package base name} MUST NOT include the .tar.gz suffix + @goto :EOF +) +@%escript% %nodetool% -sname "%node_name%" -setcookie "%erlang_cookie%" upgrade %2 +@goto :EOF + +:set_trim +@set %1=%2 +@goto :EOF diff --git a/rel/files/nodetool b/rel/files/nodetool new file mode 100755 index 0000000..a97f756 --- /dev/null +++ b/rel/files/nodetool @@ -0,0 +1,219 @@ +#!/usr/bin/env escript +%% -*- 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), + + %% any commands that don't need a running node + case RestArgs of + ["chkconfig", File] -> + case file:consult(File) of + {ok, _} -> + io:format("ok\n"), + halt(0); + {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"], []), + halt(1) + end; + _ -> + ok + end, + + %% 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; + {false,pong} -> + io:format("Failed to connect to node ~p .\n", [TargetNode]), + halt(1); + {_, pang} -> + io:format("Node ~p not responding to pings.\n", [TargetNode]), + halt(1) + end, + + case RestArgs of + ["getpid"] -> + io:format("~p\n", + [list_to_integer(rpc:call(TargetNode, os, getpid, []))]); + ["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; + ["rpc_infinity", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, + list_to_atom(Module), + list_to_atom(Function), + [RpcArgs], infinity) 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; + ["eval", Str0] -> + Str = string:strip(Str0, right, $.) ++ ".", + Bindings = erl_eval:new_bindings(), + case rpc:call(TargetNode, + erl_eval, + exprs, + [parse(Str), Bindings], + 60000) of + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + {value, Value, _Bindings} -> + io:format("~p\n", [Value]) + end; + ["upgrade", ReleasePackage] -> + %% TODO: This script currently does NOT support slim releases. + %% Necessary steps to upgrade a slim release are as follows: + %% 1. unpack relup archive manually + %% 2. copy releases directory and necessary libraries + %% 3. using release_hander:set_unpacked/2 . + %% For more details, see https://github.com/rebar/rebar/pull/52 + %% and https://github.com/rebar/rebar/issues/202 + {ok, Vsn} = rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackage], 60000), + io:format("Unpacked Release ~p\n", [Vsn]), + {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, + check_install_release, [Vsn], 60000), + {ok, OtherVsn, Desc} = rpc:call(TargetNode, release_handler, + install_release, [Vsn], 60000), + io:format("Installed Release ~p\n", [Vsn]), + ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], 60000), + io:format("Made Release ~p Permanent\n", [Vsn]); + Other -> + io:format("Other: ~p\n", [Other]), + io:format("Usage: nodetool {chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval|upgrade}\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. + +parse(Str) -> + {ok, Tokens, _} = erl_scan:string(Str), + {ok, Exprs} = erl_parse:parse_exprs(Tokens), + Exprs. diff --git a/rel/files/start_erl.cmd b/rel/files/start_erl.cmd new file mode 100644 index 0000000..c0f2072 --- /dev/null +++ b/rel/files/start_erl.cmd @@ -0,0 +1,40 @@ +@setlocal + +@rem Parse arguments. erlsrv.exe prepends erl arguments prior to first ++. +@rem Other args are position dependent. +@set args="%*" +@for /F "delims=++ tokens=1,2,3" %%I in (%args%) do @( + @set erl_args=%%I + @call :set_trim node_name %%J + @rem Trim spaces from the left of %%K (node_root), which may have spaces inside + @for /f "tokens=* delims= " %%a in ("%%K") do @set node_root=%%a +) + +@set releases_dir=%node_root%\releases + +@rem parse ERTS version and release version from start_erl.dat +@for /F "usebackq tokens=1,2" %%I in ("%releases_dir%\start_erl.data") do @( + @call :set_trim erts_version %%I + @call :set_trim release_version %%J +) + +@set erl_exe="%node_root%\erts-%erts_version%\bin\erl.exe" +@set boot_file="%releases_dir%\%release_version%\%node_name%" + +@if exist "%releases_dir%\%release_version%\sys.config" ( + @set app_config="%releases_dir%\%release_version%\sys.config" +) else ( + @set app_config="%node_root%\etc\app.config" +) + +@if exist "%releases_dir%\%release_version%\vm.args" ( + @set vm_args="%releases_dir%\%release_version%\vm.args" +) else ( + @set vm_args="%node_root%\etc\vm.args" +) + +@%erl_exe% %erl_args% -boot %boot_file% -config %app_config% -args_file %vm_args% + +:set_trim +@set %1=%2 +@goto :EOF diff --git a/rel/files/sys.config b/rel/files/sys.config new file mode 100644 index 0000000..bc9bd07 --- /dev/null +++ b/rel/files/sys.config @@ -0,0 +1,85 @@ +%% Example configuration for Guam. +[ + { + kolab_guam, [ + { + imap_servers, [ + { + imap, [ + { host, "127.0.0.1" }, + { port, 143 }, + { tls, starttls } + ] + }, + { + imaps, [ + { host, "127.0.0.1" }, + { port, 993 }, + { tls, true } + ] + } + ] + }, + { + listeners, [ + { + imap, [ + { port, 9143 }, + { imap_server, imap }, + { + rules, [ + { filter_groupware, [] } + ] + }, + { + tls_config, [ + { certfile, "/etc/pki/tls/private/localhost.pem" } + ] + } + ] + }, + { + imaps, [ + { port, 9993 }, + { implicit_tls, true }, + { imap_server, imaps }, + { + rules, [ + { filter_groupware, [] } + ] + }, + { + tls_config, [ + { certfile, "/etc/pki/tls/private/localhost.pem" } + ] + } + ] + } + ] + } + ] + }, + + { + lager, [ + { + handlers, [ + { lager_console_backend, debug }, + { lager_file_backend, [ { file, "log/error.log"}, { level, error } ] }, + { lager_file_backend, [ { file, "log/console.log"}, { level, info } ] } + ] + } + ] + }, + + %% SASL config + { + sasl, [ + { sasl_error_logger, { file, "log/sasl-error.log" } }, + { errlog_type, error }, + { error_logger_mf_dir, "log/sasl" }, % Log directory + { error_logger_mf_maxbytes, 10485760 }, % 10 MB max file size + { error_logger_mf_maxfiles, 5 } % 5 files max + ] + } +]. diff --git a/rel/files/vm.args b/rel/files/vm.args new file mode 100644 index 0000000..f62ae27 --- /dev/null +++ b/rel/files/vm.args @@ -0,0 +1,19 @@ +## Name of the node +-name kolab_guam@127.0.0.1 + +## Cookie for distributed erlang +-setcookie kolab_guam + +## 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 5 + +## Increase number of concurrent ports/sockets +##-env ERL_MAX_PORTS 4096 + +## Tweak GC to run more often +##-env ERL_FULLSWEEP_AFTER 10 diff --git a/rel/reltool.config b/rel/reltool.config new file mode 100644 index 0000000..1ece54f --- /dev/null +++ b/rel/reltool.config @@ -0,0 +1,51 @@ +%% -*- mode: erlang -*- +%% ex: ft=erlang +{sys, [ + {lib_dirs, ["../deps"]}, + {erts, [{mod_cond, derived}, {app_file, strip}]}, + {app_file, strip}, + {rel, "kolab_guam", "0.9", + [ + kernel, + stdlib, + sasl, + compiler, + syntax_tools, + goldrush, + lager, + lager_syslog, + crypto, + kolab_guam + ]}, + {rel, "start_clean", "", + [ + kernel, + stdlib + ]}, + {boot_rel, "kolab_guam"}, + {profile, embedded}, + {incl_cond, derived}, + {excl_archive_filters, [".*"]}, %% Do not archive built libs + {excl_sys_filters, ["^bin/(?!start_clean.boot)", + "^erts.*/bin/(dialyzer|typer)", + "^erts.*/(doc|info|include|lib|man|src)"]}, + {excl_app_filters, ["\.gitignore"]}, + {app, kolab_guam, [{mod_cond, app}, {incl_cond, include}, {lib_dir, "../apps/kolab_guam"}]} + ]}. + +{target_dir, "kolab_guam"}. + +{overlay, [ + {mkdir, "log/sasl"}, + {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"}, + {copy, "files/nodetool", "releases/\{\{rel_vsn\}\}/nodetool"}, + {copy, "kolab_guam/bin/start_clean.boot", + "\{\{erts_vsn\}\}/bin/start_clean.boot"}, + {copy, "files/kolab_guam", "bin/kolab_guam"}, + {copy, "files/kolab_guam.cmd", "bin/kolab_guam.cmd"}, + {copy, "files/start_erl.cmd", "bin/start_erl.cmd"}, + %% Following line may be safely removed in new projects + {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"}, + {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"}, + {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"} + ]}.