diff options
Diffstat (limited to 'src/rlx_prv_assembler.erl')
-rw-r--r-- | src/rlx_prv_assembler.erl | 1003 |
1 files changed, 1003 insertions, 0 deletions
diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl new file mode 100644 index 0000000..aca783d --- /dev/null +++ b/src/rlx_prv_assembler.erl @@ -0,0 +1,1003 @@ +%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*- +%%% Copyright 2012 Erlware, LLC. All Rights Reserved. +%%% +%%% This file is provided to you under the Apache License, +%%% Version 2.0 (the "License"); you may not use this file +%%% except in compliance with the License. You may obtain +%%% a copy of the License at +%%% +%%% http://www.apache.org/licenses/LICENSE-2.0 +%%% +%%% Unless required by applicable law or agreed to in writing, +%%% software distributed under the License is distributed on an +%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%%% KIND, either express or implied. See the License for the +%%% specific language governing permissions and limitations +%%% under the License. +%%%--------------------------------------------------------------------------- +%%% @author Eric Merritt <[email protected]> +%%% @copyright (C) 2012 Erlware, LLC. +%%% +%%% @doc Given a complete built release this provider assembles that release +%%% into a release directory. +-module(rcl_prv_assembler). + +-behaviour(rcl_provider). + +-export([init/1, + do/1, + format_error/1]). + +-include_lib("relcool/include/relcool.hrl"). + +%%============================================================================ +%% API +%%============================================================================ +-spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +init(State) -> + {ok, State}. + +%% @doc recursively dig down into the library directories specified in the state +%% looking for OTP Applications +-spec do(rcl_state:t()) -> {ok, rcl_state:t()} | relcool:error(). +do(State) -> + {RelName, RelVsn} = rcl_state:default_configured_release(State), + Release = rcl_state:get_realized_release(State, RelName, RelVsn), + OutputDir = rcl_state:output_dir(State), + case create_output_dir(OutputDir) of + ok -> + case rcl_release:realized(Release) of + true -> + copy_app_directories_to_output(State, Release, OutputDir); + false -> + ?RCL_ERROR({unresolved_release, RelName, RelVsn}) + end; + Error -> + Error + end. + +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error({unresolved_release, RelName, RelVsn}) -> + io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); +format_error({ec_file_error, AppDir, TargetDir, E}) -> + io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", + [AppDir, TargetDir, E]); +format_error({config_does_not_exist, Path}) -> + io_lib:format("The config file specified for this release (~s) does not exist!", + [Path]); +format_error({specified_erts_does_not_exist, ErtsVersion}) -> + io_lib:format("Specified version of erts (~s) does not exist", + [ErtsVersion]); +format_error({release_script_generation_error, RelFile}) -> + io_lib:format("Unknown internal release error generating the release file to ~s", + [RelFile]); +format_error({release_script_generation_warning, Module, Warnings}) -> + ["Warnings generating release \s", + rcl_util:indent(1), Module:format_warning(Warnings)]; +format_error({unable_to_create_output_dir, OutputDir}) -> + io_lib:format("Unable to create output directory (possible permissions issue): ~s", + [OutputDir]); +format_error({release_script_generation_error, Module, Errors}) -> + ["Errors generating release \n", + rcl_util:indent(1), Module:format_error(Errors)]; +format_error({relup_generation_error, CurrentName, UpFromName}) -> + io_lib:format("Unknown internal release error generating the relup from ~s to ~s", + [UpFromName, CurrentName]); +format_error({relup_generation_warning, Module, Warnings}) -> + ["Warnings generating relup \s", + rcl_util:indent(1), Module:format_warning(Warnings)]; +format_error({relup_script_generation_error, + {relupcript_generation_error, systools_relup, + {missing_sasl, _}}}) -> + "Unfortunately, due to requirements in systools, you need to have the sasl application " + "in both the current release and the release to upgrade from."; +format_error({relup_script_generation_error, Module, Errors}) -> + ["Errors generating relup \n", + rcl_util:indent(1), Module:format_error(Errors)]; +format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> + io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", + [AppDir, TargetDir, rcl_util:indent(1), + file:format_error(Reason)]). + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec create_output_dir(file:name()) -> + ok | {error, Reason::term()}. +create_output_dir(OutputDir) -> + case filelib:is_dir(OutputDir) of + false -> + case rcl_util:mkdir_p(OutputDir) of + ok -> + ok; + {error, _} -> + ?RCL_ERROR({unable_to_create_output_dir, OutputDir}) + end; + true -> + ok + end. + +copy_app_directories_to_output(State, Release, OutputDir) -> + LibDir = filename:join([OutputDir, "lib"]), + ok = ec_file:mkdir_p(LibDir), + Apps = rcl_release:application_details(Release), + Result = lists:filter(fun({error, _}) -> + true; + (_) -> + false + end, + lists:flatten(ec_plists:map(fun(App) -> + copy_app(LibDir, App) + end, Apps))), + case Result of + [E | _] -> + E; + [] -> + create_release_info(State, Release, OutputDir) + end. + +copy_app(LibDir, App) -> + AppName = erlang:atom_to_list(rcl_app_info:name(App)), + AppVsn = rcl_app_info:vsn_as_string(App), + AppDir = rcl_app_info:dir(App), + TargetDir = filename:join([LibDir, AppName ++ "-" ++ AppVsn]), + if + AppDir == TargetDir -> + %% No need to do anything here, discover found something already in + %% a release dir + ok; + true -> + copy_app(App, AppDir, TargetDir) + end. + +copy_app(App, AppDir, TargetDir) -> + remove_symlink_or_directory(TargetDir), + case rcl_app_info:link(App) of + true -> + link_directory(AppDir, TargetDir); + false -> + copy_directory(AppDir, TargetDir) + end. + +remove_symlink_or_directory(TargetDir) -> + case ec_file:is_symlink(TargetDir) of + true -> + ec_file:remove(TargetDir); + false -> + case filelib:is_dir(TargetDir) of + true -> + ok = ec_file:remove(TargetDir, [recursive]); + false -> + ok + end + end. + +link_directory(AppDir, TargetDir) -> + case file:make_symlink(AppDir, TargetDir) of + {error, Reason} -> + ?RCL_ERROR({unable_to_make_symlink, AppDir, TargetDir, Reason}); + ok -> + ok + end. + +copy_directory(AppDir, TargetDir) -> + ec_plists:map(fun(SubDir) -> + copy_dir(AppDir, TargetDir, SubDir) + end, ["ebin", + "include", + "priv", + "src", + "c_src", + "README", + "LICENSE"]). + +copy_dir(AppDir, TargetDir, SubDir) -> + SubSource = filename:join(AppDir, SubDir), + SubTarget = filename:join(TargetDir, SubDir), + case filelib:is_dir(SubSource) of + true -> + ok = rcl_util:mkdir_p(SubTarget), + case ec_file:copy(SubSource, SubTarget, [recursive]) of + {error, E} -> + ?RCL_ERROR({ec_file_error, AppDir, SubTarget, E}); + ok -> + ok + end; + false -> + ok + end. + +create_release_info(State0, Release0, OutputDir) -> + RelName = erlang:atom_to_list(rcl_release:name(Release0)), + ReleaseDir = release_output_dir(State0, Release0), + ReleaseFile = filename:join([ReleaseDir, RelName ++ ".rel"]), + ok = ec_file:mkdir_p(ReleaseDir), + Release1 = rcl_release:relfile(Release0, ReleaseFile), + State1 = rcl_state:update_realized_release(State0, Release1), + case rcl_release:metadata(Release1) of + {ok, Meta} -> + ok = ec_file:write_term(ReleaseFile, Meta), + write_bin_file(State1, Release1, OutputDir, ReleaseDir); + E -> + E + end. + + +write_bin_file(State, Release, OutputDir, RelDir) -> + RelName = erlang:atom_to_list(rcl_release:name(Release)), + RelVsn = rcl_release:vsn(Release), + BinDir = filename:join([OutputDir, "bin"]), + ok = ec_file:mkdir_p(BinDir), + VsnRel = filename:join(BinDir, rcl_release:canonical_name(Release)), + BareRel = filename:join(BinDir, RelName), + ErlOpts = rcl_state:get(State, erl_opts, ""), + StartFile = case rcl_state:get(State, extended_start_script, false) of + false -> + bin_file_contents(RelName, RelVsn, + rcl_release:erts(Release), + ErlOpts); + true -> + extended_bin_file_contents(RelName, RelVsn, rcl_release:erts(Release), ErlOpts) + end, + %% We generate the start script by default, unless the user + %% tells us not too + case rcl_state:get(State, generate_start_script, true) of + false -> + ok; + _ -> + ok = file:write_file(VsnRel, StartFile), + ok = file:change_mode(VsnRel, 8#777), + ok = file:write_file(BareRel, StartFile), + ok = file:change_mode(BareRel, 8#777) + end, + ok = file:write_file(filename:join(RelDir, "vm.args"), vm_args_file(RelName)), + copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir). + +%% @doc copy config/sys.config or generate one to releases/VSN/sys.config +-spec copy_or_generate_sys_config_file(rcl_state:t(), rcl_release:t(), + file:name(), file:name()) -> + {ok, rcl_state:t()} | relcool:error(). +copy_or_generate_sys_config_file(State, Release, OutputDir, RelDir) -> + RelSysConfPath = filename:join([RelDir, "sys.config"]), + case rcl_state:sys_config(State) of + undefined -> + ok = generate_sys_config_file(RelSysConfPath), + include_erts(State, Release, OutputDir, RelDir); + ConfigPath -> + case filelib:is_regular(ConfigPath) of + false -> + ?RCL_ERROR({config_does_not_exist, ConfigPath}); + true -> + ok = ec_file:copy(ConfigPath, RelSysConfPath), + include_erts(State, Release, OutputDir, RelDir) + end + end. + +%% @doc write a generic sys.config to the path RelSysConfPath +-spec generate_sys_config_file(string()) -> ok. +generate_sys_config_file(RelSysConfPath) -> + {ok, Fd} = file:open(RelSysConfPath, [write]), + io:format(Fd, + "%% Thanks to Ulf Wiger at Ericcson for these comments:~n" + "%%~n" + "%% This file is identified via the erl command line option -config File.~n" + "%% Note that File should have no extension, e.g.~n" + "%% erl -config .../sys (if this file is called sys.config)~n" + "%%~n" + "%% In this file, you can redefine application environment variables.~n" + "%% This way, you don't have to modify the .app files of e.g. OTP applications.~n" + "[].~n", []), + file:close(Fd). + +%% @doc Optionally add erts directory to release, if defined. +-spec include_erts(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> {ok, rcl_state:t()} | relcool:error(). +include_erts(State, Release, OutputDir, RelDir) -> + case rcl_state:get(State, include_erts, true) of + true -> + Prefix = code:root_dir(), + ErtsVersion = rcl_release:erts(Release), + ErtsDir = filename:join([Prefix, "erts-" ++ ErtsVersion]), + LocalErts = filename:join([OutputDir, "erts-" ++ ErtsVersion]), + case filelib:is_dir(ErtsDir) of + false -> + ?RCL_ERROR({specified_erts_does_not_exist, ErtsVersion}); + true -> + ok = ec_file:mkdir_p(LocalErts), + ok = ec_file:copy(ErtsDir, LocalErts, [recursive]), + ok = ec_file:remove(filename:join([LocalErts, "bin", "erl"])), + ok = file:write_file(filename:join([LocalErts, "bin", "erl"]), erl_script(ErtsVersion)), + case rcl_state:get(State, extended_start_script, false) of + true -> + ok = ec_file:copy(filename:join([Prefix, "bin", "start_clean.boot"]), + filename:join([OutputDir, "bin", "start_clean.boot"])), + NodeToolFile = nodetool_contents(), + NodeTool = filename:join([LocalErts, "bin", "nodetool"]), + ok = file:write_file(NodeTool, NodeToolFile), + ok = file:change_mode(NodeTool, 8#755); + false -> + ok + end, + make_boot_script(State, Release, OutputDir, RelDir) + end; + _ -> + make_boot_script(State, Release, OutputDir, RelDir) + end. + + +-spec make_boot_script(rcl_state:t(), rcl_release:t(), file:name(), file:name()) -> + {ok, rcl_state:t()} | relcool:error(). +make_boot_script(State, Release, OutputDir, RelDir) -> + Options = [{path, [RelDir | get_code_paths(Release, OutputDir)]}, + {outdir, RelDir}, + no_module_tests, silent], + Name = erlang:atom_to_list(rcl_release:name(Release)), + ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), + case make_script(Options, + fun(CorrectedOptions) -> + systools:make_script(Name, CorrectedOptions) + end) of + ok -> + rcl_log:error(rcl_state:log(State), + "release successfully created!"), + make_relup(State, Release); + error -> + ?RCL_ERROR({release_script_generation_error, ReleaseFile}); + {ok, _, []} -> + rcl_log:error(rcl_state:log(State), + "release successfully created!"), + make_relup(State, Release); + {ok,Module,Warnings} -> + ?RCL_ERROR({release_script_generation_warn, Module, Warnings}); + {error,Module,Error} -> + ?RCL_ERROR({release_script_generation_error, Module, Error}) + end. + +-spec make_script([term()], + fun(([term()]) -> Res)) -> Res. +make_script(Options, RunFun) -> + %% Erts 5.9 introduced a non backwards compatible option to + %% erlang this takes that into account + Erts = erlang:system_info(version), + case ec_semver:gte(Erts, "5.9") of + true -> + RunFun([no_warn_sasl | Options]); + _ -> + RunFun(Options) + end. + +make_relup(State, Release) -> + case rcl_state:action(State) of + relup -> + UpFrom = + case rcl_state:upfrom(State) of + undefined -> + get_last_release(State, Release); + Vsn -> + get_up_release(State, Release, Vsn) + end, + case UpFrom of + undefined -> + ?RCL_ERROR(no_upfrom_release_found); + _ -> + make_upfrom_script(State, Release, UpFrom) + end; + _ -> + {ok, State} + end. + +make_upfrom_script(State, Release, UpFrom) -> + OutputDir = rcl_state:output_dir(State), + Options = [{outdir, OutputDir}, + {path, get_code_paths(Release, OutputDir) ++ + get_code_paths(UpFrom, OutputDir)}, + silent], + CurrentRel = strip_rel(rcl_release:relfile(Release)), + UpFromRel = strip_rel(rcl_release:relfile(UpFrom)), + rcl_log:debug(rcl_state:log(State), + "systools:make_relup(~p, ~p, ~p, ~p)", + [CurrentRel, UpFromRel, UpFromRel, Options]), + case make_script(Options, + fun(CorrectOptions) -> + systools:make_relup(CurrentRel, [UpFromRel], [UpFromRel], CorrectOptions) + end) of + ok -> + rcl_log:error(rcl_state:log(State), + "relup from ~s to ~s successfully created!", + [UpFromRel, CurrentRel]), + {ok, State}; + error -> + ?RCL_ERROR({relup_script_generation_error, CurrentRel, UpFromRel}); + {ok, RelUp, _, []} -> + rcl_log:error(rcl_state:log(State), + "relup successfully created!"), + write_relup_file(State, Release, RelUp), + {ok, State}; + {ok,_, Module,Warnings} -> + ?RCL_ERROR({relup_script_generation_warn, Module, Warnings}); + {error,Module,Errors} -> + ?RCL_ERROR({relupcript_generation_error, Module, Errors}) + end. + +write_relup_file(State, Release, Relup) -> + OutDir = release_output_dir(State, Release), + RelName = rcl_util:to_string(rcl_release:name(Release)), + RelupFile = filename:join(OutDir, RelName ++ ".relup"), + ok = ec_file:write_term(RelupFile, Relup). + +strip_rel(Name) -> + rcl_util:to_string(filename:join(filename:dirname(Name), + filename:basename(Name, ".rel"))). + + +get_up_release(State, Release, Vsn) -> + Name = rcl_release:name(Release), + try + ec_dictionary:get({Name, Vsn}, rcl_state:realized_releases(State)) + catch + throw:notfound -> + undefined + end. + +get_last_release(State, Release) -> + Releases0 = [Rel || {{_, _}, Rel} <- ec_dictionary:to_list(rcl_state:realized_releases(State))], + Releases1 = lists:sort(fun(R1, R2) -> + ec_semver:lte(rcl_release:vsn(R1), + rcl_release:vsn(R2)) + end, Releases0), + Res = lists:foldl(fun(_Rel, R = {found, _}) -> + R; + (Rel, Prev) -> + case rcl_release:vsn(Rel) == rcl_release:vsn(Release) of + true -> + {found, Prev}; + false -> + Rel + end + end, undefined, Releases1), + case Res of + {found, R} -> + R; + Else -> + Else + end. + +-spec release_output_dir(rcl_state:t(), rcl_release:t()) -> string(). +release_output_dir(State, Release) -> + OutputDir = rcl_state:output_dir(State), + filename:join([OutputDir, + "releases", + rcl_release:canonical_name(Release)]). + +%% @doc Generates the correct set of code paths for the system. +-spec get_code_paths(rcl_release:t(), file:name()) -> [file:name()]. +get_code_paths(Release, OutDir) -> + LibDir = filename:join(OutDir, "lib"), + [filename:join([LibDir, + erlang:atom_to_list(rcl_app_info:name(App)) ++ "-" ++ + rcl_app_info:vsn_as_string(App), "ebin"]) || + App <- rcl_release:application_details(Release)]. + +erl_script(ErtsVsn) -> + [<<"#!/bin/sh +set -e + +SCRIPT_DIR=`dirname $0` +ROOTDIR=`cd $SCRIPT_DIR/../../ && pwd` +BINDIR=$ROOTDIR/erts-">>, ErtsVsn, <<"/bin +EMU=beam +PROGNAME=`echo $0 | sed 's/.*\\///'` +export EMU +export ROOTDIR +export BINDIR +export PROGNAME +exec \"$BINDIR/erlexec\" ${1+\"$@\"} +">>]. + +bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) -> + [<<"#!/bin/sh + +set -e + +SCRIPT_DIR=`dirname $0` +RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` +REL_NAME=">>, RelName, <<" +REL_VSN=">>, RelVsn, <<" +ERTS_VSN=">>, ErtsVsn, <<" +REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN +ERL_OPTS=">>, ErlOpts, <<" + +find_erts_dir() { + local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN + if [ -d \"$erts_dir\" ]; then + ERTS_DIR=$erts_dir; + ROOTDIR=$RELEASE_ROOT_DIR + else + local erl=`which erl` + local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop` + ERTS_DIR=$erl_root/erts-$ERTS_VSN + ROOTDIR=$erl_root + fi + +} + +find_sys_config() { + local possible_sys=$REL_DIR/sys.config + if [ -f \"$possible_sys\" ]; then + SYS_CONFIG=\"-config $possible_sys\" + fi +} + +find_erts_dir +find_sys_config +export ROOTDIR=$RELEASE_ROOT_DIR +export BINDIR=$ERTS_DIR/bin +export EMU=beam +export PROGNAME=erl +export LD_LIBRARY_PATH=$ERTS_DIR/lib + +cd $ROOTDIR + +$BINDIR/erlexec $ERL_OPTS $SYS_CONFIG -boot $REL_DIR/$REL_NAME $@">>]. + +extended_bin_file_contents(RelName, RelVsn, ErtsVsn, ErlOpts) -> + [<<"#!/bin/sh + +set -e + +SCRIPT_DIR=`dirname $0` +RELEASE_ROOT_DIR=`cd $SCRIPT_DIR/.. && pwd` +REL_NAME=">>, RelName, <<" +REL_VSN=">>, RelVsn, <<" +ERTS_VSN=">>, ErtsVsn, <<" +REL_DIR=$RELEASE_ROOT_DIR/releases/$REL_NAME-$REL_VSN +ERL_OPTS=">>, ErlOpts, <<" +PIPE_DIR=/tmp/$REL_DIR/ + +find_erts_dir() { + local erts_dir=$RELEASE_ROOT_DIR/erts-$ERTS_VSN + if [ -d \"$erts_dir\" ]; then + ERTS_DIR=$erts_dir; + ROOTDIR=$RELEASE_ROOT_DIR + else + local erl=`which erl` + local erl_root=`$erl -noshell -eval \"io:format(\\\"~s\\\", [code:root_dir()]).\" -s init stop` + ERTS_DIR=$erl_root/erts-$ERTS_VSN + ROOTDIR=$erl_root + fi + +} + +find_sys_config() { + local possible_sys=$REL_DIR/sys.config + if [ -f \"$possible_sys\" ]; then + SYS_CONFIG=\"-config $possible_sys\" + fi +} + +# 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=$REL_DIR + if [ -e \"$REL_DIR/vm.args\" ]; then + VMARGS_PATH=\"$REL_DIR/vm.args\" + else + VMARGS_PATH=\"$REL_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 \"$REL_DIR/sys.config\" ]; then + CONFIG_PATH=\"$REL_DIR/sys.config\" + else + CONFIG_PATH=\"$REL_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}'` + +# Note the `date +%s`, used to allow multiple remsh to the same node transparently +REMSH_NAME_ARG=\"$REMSH_TYPE remsh`date +%s`@`echo $REMSH_NAME | awk -F@ '{print $2}'`\" +REMSH_REMSH_ARG=\"-remsh $REMSH_NAME\" + +# 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 + +# Setup remote shell command to control node +REMSH=\"$ERTS_PATH/erl $REMSH_NAME_ARG $REMSH_REMSH_ARG $COOKIE_ARG\" + +find_erts_dir +find_sys_config +export ROOTDIR=$RELEASE_ROOT_DIR +export BINDIR=$ERTS_DIR/bin +export EMU=beam +export PROGNAME=erl +export LD_LIBRARY_PATH=$ERTS_DIR/lib + +cd $ROOTDIR + +# Setup command to control the node +NODETOOL=\"$ERTS_DIR/bin/escript $ERTS_DIR/bin/nodetool $NAME_ARG $COOKIE_ARG\" + +# Check the first argument for instructions +case \"$1\" in + start|start_boot) + + # Make sure there is not already a node running + #RES=`$NODETOOL ping` + #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=\"$SCRIPT_DIR/bin/$REL_NAME $HEART_OPTION $RUN_PARAM\" + export HEART_COMMAND + mkdir -p $PIPE_DIR + $ERTS_DIR/bin/run_erl -daemon $PIPE_DIR $RUNNER_LOG_DIR \"exec $RELEASE_ROOT_DIR/bin/$REL_NAME $START_OPTION $RUN_PARAM\" 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 \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $1}'` + ;; + SunOS) + # PID COMMAND + PID=`ps -ef -o pid= -o args=| + grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $1}'` + ;; + CYGWIN*) + # UID PID PPID TTY STIME COMMAND + PID=`ps -efW|grep \"$SCRIPT_DIR/.*/[b]eam\"|awk '{print $2}'` + ;; + esac + $NODETOOL stop + ES=$? + if [ \"$ES\" -ne 0 ]; then + exit $ES + fi + while `kill -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 + $NODETOOL ping + ES=$? + if [ \"$ES\" -ne 0 ]; then + exit $ES + fi + ;; + + attach) + # Make sure a node IS running + RES=`$NODETOOL ping` + ES=$? + if [ \"$ES\" -ne 0 ]; then + echo \"Node is not running!\" + exit $ES + fi + + shift + exec $ERTS_DIR/bin/to_erl $PIPE_DIR + ;; + + remote_console) + # Make sure a node IS running + RES=`$NODETOOL ping` + 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: $REL_NAME 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 + RES=`$NODETOOL ping` + ES=$? + if [ \"$ES\" -ne 0 ]; then + echo \"Node is not running!\" + exit $ES + fi + + node_name=`echo $NAME_ARG | awk '{print $2}'` + erlang_cookie=`echo $COOKIE_ARG | awk '{print $2}'` + + $ERTS_DIR/bin/escript $SCRIPT_DIR/bin/install_upgrade.escript $node_name $erlang_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) BOOTFILE=$REL_NAME ;; + console_clean) BOOTFILE=start_clean ;; + console_boot) + shift + BOOTFILE=\"$1\" + shift + ;; + esac + # Setup beam-required vars + ROOTDIR=$RELEASE_ROOT_DIR + BINDIR=$RELEASE_ROOT_DIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*\\///'` + CMD=\"$BINDIR/erlexec -boot $REL_DIR/$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 \"$REL_NAME[$$]\" \"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=$REL_NAME + FOREGROUNDOPTIONS=\"-noinput +Bd\" + + # Setup beam-required vars + ROOTDIR=$RELEASE_ROOT_DIR + BINDIR=$RELEASE_ROOT_DIR/erts-$ERTS_VSN/bin + EMU=beam + PROGNAME=`echo $0 | sed 's/.*///'` + CMD=\"$BINDIR/erlexec $FOREGROUNDOPTIONS -boot $REL_DIR/$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+\"$@\"} + ;; + *) + echo \"Usage: $REL_NAME {start|start_boot <file>|foreground|stop|restart|reboot|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade}\" + exit 1 + ;; +esac + +exit 0">>]. + +nodetool_contents() -> + [<<"%% -*- 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.">>]. + +vm_args_file(RelName) -> + [<<"## Name of the node +-name ">>, RelName, <<"@127.0.0.1 + +## Cookie for distributed erlang +-setcookie ">>, RelName, <<" + +## 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">>]. |