The `install_upgrade.escript` depended on being able to retrieve the `vm_args` argument from `init`, but that options has been removed recently. Since this backup was unused, we also remove it here.
379 lines
16 KiB
Erlang
Executable File
379 lines
16 KiB
Erlang
Executable File
#!/usr/bin/env escript
|
||
%%! -noshell -noinput
|
||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
||
%% ex: ft=erlang ts=4 sw=4 et
|
||
|
||
-define(TIMEOUT, 300000).
|
||
-define(INFO(Fmt,Args), io:format(Fmt++"~n",Args)).
|
||
|
||
main([Command0, DistInfoStr | CommandArgs]) ->
|
||
%% convert the distribution info arguments string to an erlang term
|
||
{ok, Tokens, _} = erl_scan:string(DistInfoStr ++ "."),
|
||
{ok, DistInfo} = erl_parse:parse_term(Tokens),
|
||
%% convert arguments into a proplist
|
||
Opts = parse_arguments(CommandArgs),
|
||
%% invoke the command passed as argument
|
||
F = case Command0 of
|
||
"install" -> fun(A, B) -> install(A, B) end;
|
||
"unpack" -> fun(A, B) -> unpack(A, B) end;
|
||
"upgrade" -> fun(A, B) -> upgrade(A, B) end;
|
||
"downgrade" -> fun(A, B) -> downgrade(A, B) end;
|
||
"uninstall" -> fun(A, B) -> uninstall(A, B) end;
|
||
"versions" -> fun(A, B) -> versions(A, B) end
|
||
end,
|
||
F(DistInfo, Opts);
|
||
main(Args) ->
|
||
?INFO("unknown args: ~p", [Args]),
|
||
erlang:halt(1).
|
||
|
||
unpack({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||
Version = proplists:get_value(version, Opts),
|
||
case unpack_release(RelName, TargetNode, Version) of
|
||
{ok, Vsn} ->
|
||
?INFO("Unpacked successfully: ~p", [Vsn]);
|
||
old ->
|
||
%% no need to unpack, has been installed previously
|
||
?INFO("Release ~s is marked old.",[Version]);
|
||
unpacked ->
|
||
?INFO("Release ~s is already unpacked.",[Version]);
|
||
current ->
|
||
?INFO("Release ~s is already installed and current.",[Version]);
|
||
permanent ->
|
||
?INFO("Release ~s is already installed and set permanent.",[Version]);
|
||
{error, Reason} ->
|
||
?INFO("Unpack failed: ~p.",[Reason]),
|
||
print_existing_versions(TargetNode),
|
||
erlang:halt(2)
|
||
end;
|
||
unpack(_, Args) ->
|
||
?INFO("unpack: unknown args ~p", [Args]).
|
||
|
||
install({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||
Version = proplists:get_value(version, Opts),
|
||
case unpack_release(RelName, TargetNode, Version) of
|
||
{ok, Vsn} ->
|
||
?INFO("Unpacked successfully: ~p.", [Vsn]),
|
||
check_and_install(TargetNode, Vsn),
|
||
maybe_permafy(TargetNode, RelName, Vsn, Opts);
|
||
old ->
|
||
%% no need to unpack, has been installed previously
|
||
?INFO("Release ~s is marked old, switching to it.",[Version]),
|
||
check_and_install(TargetNode, Version),
|
||
maybe_permafy(TargetNode, RelName, Version, Opts);
|
||
unpacked ->
|
||
?INFO("Release ~s is already unpacked, now installing.",[Version]),
|
||
check_and_install(TargetNode, Version),
|
||
maybe_permafy(TargetNode, RelName, Version, Opts);
|
||
current ->
|
||
case proplists:get_value(permanent, Opts, true) of
|
||
true ->
|
||
?INFO("Release ~s is already installed and current, making permanent.",
|
||
[Version]),
|
||
permafy(TargetNode, RelName, Version);
|
||
false ->
|
||
?INFO("Release ~s is already installed and current.",
|
||
[Version])
|
||
end;
|
||
permanent ->
|
||
%% this release is marked permanent, however it might not the
|
||
%% one currently running
|
||
case current_release_version(TargetNode) of
|
||
Version ->
|
||
?INFO("Release ~s is already installed, running and set permanent.",
|
||
[Version]);
|
||
CurrentVersion ->
|
||
?INFO("Release ~s is the currently running version.",
|
||
[CurrentVersion]),
|
||
check_and_install(TargetNode, Version),
|
||
maybe_permafy(TargetNode, RelName, Version, Opts)
|
||
end;
|
||
{error, Reason} ->
|
||
?INFO("Unpack failed: ~p",[Reason]),
|
||
print_existing_versions(TargetNode),
|
||
erlang:halt(2)
|
||
end;
|
||
install(_, Args) ->
|
||
?INFO("install: unknown args ~p", [Args]).
|
||
|
||
upgrade(DistInfo, Args) ->
|
||
install(DistInfo, Args).
|
||
|
||
downgrade(DistInfo, Args) ->
|
||
install(DistInfo, Args).
|
||
|
||
uninstall({_RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
|
||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||
WhichReleases = which_releases(TargetNode),
|
||
Version = proplists:get_value(version, Opts),
|
||
case proplists:get_value(Version, WhichReleases) of
|
||
undefined ->
|
||
?INFO("Release ~s is already uninstalled.", [Version]);
|
||
old ->
|
||
?INFO("Release ~s is marked old, uninstalling it.", [Version]),
|
||
remove_release(TargetNode, Version);
|
||
unpacked ->
|
||
?INFO("Release ~s is marked unpacked, uninstalling it",
|
||
[Version]),
|
||
remove_release(TargetNode, Version);
|
||
current ->
|
||
?INFO("Uninstall failed: Release ~s is marked current.", [Version]),
|
||
erlang:halt(2);
|
||
permanent ->
|
||
?INFO("Uninstall failed: Release ~s is running.", [Version]),
|
||
erlang:halt(2)
|
||
end;
|
||
uninstall(_, Args) ->
|
||
?INFO("uninstall: unknown args ~p", [Args]).
|
||
|
||
versions({_RelName, NameTypeArg, NodeName, Cookie}, []) ->
|
||
TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
|
||
print_existing_versions(TargetNode).
|
||
|
||
parse_arguments(Args) ->
|
||
parse_arguments(Args, []).
|
||
|
||
parse_arguments([], Acc) -> Acc;
|
||
parse_arguments(["--no-permanent"|Rest], Acc) ->
|
||
parse_arguments(Rest, [{permanent, false}] ++ Acc);
|
||
parse_arguments([VersionStr|Rest], Acc) ->
|
||
Version = parse_version(VersionStr),
|
||
parse_arguments(Rest, [{version, Version}] ++ Acc).
|
||
|
||
unpack_release(RelName, TargetNode, Version) ->
|
||
WhichReleases = which_releases(TargetNode),
|
||
case proplists:get_value(Version, WhichReleases) of
|
||
undefined ->
|
||
%% not installed, so unpack tarball:
|
||
%% look for a release package with the intended version in the following order:
|
||
%% releases/<relname>-<version>.tar.gz
|
||
%% releases/<version>/<relname>-<version>.tar.gz
|
||
%% releases/<version>/<relname>.tar.gz
|
||
case find_and_link_release_package(Version, RelName) of
|
||
{_, undefined} ->
|
||
{error, release_package_not_found};
|
||
{ReleasePackage, ReleasePackageLink} ->
|
||
?INFO("Release ~s not found, attempting to unpack ~s",
|
||
[Version, ReleasePackage]),
|
||
case rpc:call(TargetNode, release_handler, unpack_release,
|
||
[ReleasePackageLink], ?TIMEOUT) of
|
||
{ok, Vsn} -> {ok, Vsn};
|
||
{error, _} = Error -> Error
|
||
end
|
||
end;
|
||
Other -> Other
|
||
end.
|
||
|
||
%% 1. look for a release package tarball with the provided version in the following order:
|
||
%% releases/<relname>-<version>.tar.gz
|
||
%% releases/<version>/<relname>-<version>.tar.gz
|
||
%% releases/<version>/<relname>.tar.gz
|
||
%% 2. create a symlink from a fixed location (ie. releases/<version>/<relname>.tar.gz)
|
||
%% to the release package tarball found in 1.
|
||
%% 3. return a tuple with the paths to the release package and
|
||
%% to the symlink that is to be provided to release handler
|
||
find_and_link_release_package(Version, RelName) ->
|
||
RelNameStr = atom_to_list(RelName),
|
||
%% regardless of the location of the release package, we'll
|
||
%% always give release handler the same path which is the symlink
|
||
%% the path to the package link is relative to "releases/" because
|
||
%% that's what release handler is expecting
|
||
ReleaseHandlerPackageLink = filename:join(Version, RelNameStr),
|
||
%% this is the symlink name we'll create once
|
||
%% we've found where the actual release package is located
|
||
ReleaseLink = filename:join(["releases", Version,
|
||
RelNameStr ++ ".tar.gz"]),
|
||
ok = unpack_zipballs(RelNameStr, Version),
|
||
TarBalls = [
|
||
filename:join(["releases",
|
||
RelNameStr ++ "-" ++ Version ++ ".tar.gz"]),
|
||
filename:join(["releases", Version,
|
||
RelNameStr ++ "-" ++ Version ++ ".tar.gz"]),
|
||
filename:join(["releases", Version,
|
||
RelNameStr ++ ".tar.gz"])
|
||
],
|
||
case first_value(fun filelib:is_file/1, TarBalls) of
|
||
no_value ->
|
||
{undefined, undefined};
|
||
%% no need to create the link since the release package we
|
||
%% found is located in the same place as the link would be
|
||
{ok, Filename} when is_list(Filename) andalso
|
||
Filename =:= ReleaseLink ->
|
||
{Filename, ReleaseHandlerPackageLink};
|
||
{ok, Filename} when is_list(Filename) ->
|
||
%% we now have the location of the release package, however
|
||
%% release handler expects a fixed nomenclature (<relname>.tar.gz)
|
||
%% so give it just that by creating a symlink to the tarball
|
||
%% we found.
|
||
%% make sure that the dir where we're creating the link in exists
|
||
ok = filelib:ensure_dir(filename:join([filename:dirname(ReleaseLink), "dummy"])),
|
||
%% create the symlink pointing to the full path name of the
|
||
%% release package we found
|
||
case file:make_symlink(filename:absname(Filename), ReleaseLink) of
|
||
ok ->
|
||
ok;
|
||
{error, eperm} -> % windows!
|
||
{ok,_} = file:copy(filename:absname(Filename), ReleaseLink)
|
||
end,
|
||
{Filename, ReleaseHandlerPackageLink}
|
||
end.
|
||
|
||
unpack_zipballs(RelNameStr, Version) ->
|
||
{ok, Cwd} = file:get_cwd(),
|
||
GzFile = filename:absname(filename:join(["releases", RelNameStr ++ "-" ++ Version ++ ".tar.gz"])),
|
||
ZipFiles = filelib:wildcard(filename:join(["releases", RelNameStr ++ "-*" ++ Version ++ "*.zip"])),
|
||
?INFO("unzip ~p", [ZipFiles]),
|
||
[begin
|
||
TmdTarD="/tmp/emqx_untar_" ++ integer_to_list(erlang:system_time()),
|
||
ok = filelib:ensure_dir(filename:join([TmdTarD, "dummy"])),
|
||
{ok, _} = file:copy(Zip, filename:join([TmdTarD, "emqx.zip"])),
|
||
ok = file:set_cwd(filename:join([TmdTarD])),
|
||
{ok, _FileList} = zip:unzip("emqx.zip"),
|
||
ok = file:set_cwd(filename:join([TmdTarD, "emqx"])),
|
||
ok = erl_tar:create(GzFile, filelib:wildcard("*"), [compressed])
|
||
end || Zip <- ZipFiles],
|
||
file:set_cwd(Cwd).
|
||
|
||
first_value(_Fun, []) -> no_value;
|
||
first_value(Fun, [Value | Rest]) ->
|
||
case Fun(Value) of
|
||
false ->
|
||
first_value(Fun, Rest);
|
||
true ->
|
||
{ok, Value}
|
||
end.
|
||
|
||
parse_version(V) when is_list(V) ->
|
||
hd(string:tokens(V,"/")).
|
||
|
||
check_and_install(TargetNode, Vsn) ->
|
||
%% Backup the sys.config, this will be used when we check and install release
|
||
%% NOTE: We cannot backup the old sys.config directly, because the
|
||
%% configs for plugins are only in app-envs, not in the old sys.config
|
||
Configs0 =
|
||
[{AppName, rpc:call(TargetNode, application, get_all_env, [AppName], ?TIMEOUT)}
|
||
|| {AppName, _, _} <- rpc:call(TargetNode, application, which_applications, [], ?TIMEOUT)],
|
||
Configs1 = [{AppName, Conf} || {AppName, Conf} <- Configs0, Conf =/= []],
|
||
ok = file:write_file(filename:join(["releases", Vsn, "sys.config"]), io_lib:format("~p.", [Configs1])),
|
||
|
||
%% check and install release
|
||
case rpc:call(TargetNode, release_handler,
|
||
check_install_release, [Vsn], ?TIMEOUT) of
|
||
{ok, _OtherVsn, _Desc} ->
|
||
ok;
|
||
{error, Reason} ->
|
||
?INFO("ERROR: release_handler:check_install_release failed: ~p.",[Reason]),
|
||
erlang:halt(3)
|
||
end,
|
||
case rpc:call(TargetNode, release_handler, install_release,
|
||
[Vsn, [{update_paths, true}]], ?TIMEOUT) of
|
||
{ok, _, _} ->
|
||
?INFO("Installed Release: ~s.", [Vsn]),
|
||
ok;
|
||
{error, {no_such_release, Vsn}} ->
|
||
VerList =
|
||
iolist_to_binary(
|
||
[io_lib:format("* ~s\t~s~n",[V,S]) || {V,S} <- which_releases(TargetNode)]),
|
||
?INFO("Installed versions:~n~s", [VerList]),
|
||
?INFO("ERROR: Unable to revert to '~s' - not installed.", [Vsn]),
|
||
erlang:halt(2);
|
||
%% as described in http://erlang.org/doc/man/appup.html, when performing a relup
|
||
%% with soft purge:
|
||
%% If the value is soft_purge, release_handler:install_release/1
|
||
%% returns {error,{old_processes,Mod}}
|
||
{error, {old_processes, Mod}} ->
|
||
?INFO("ERROR: unable to install '~s' - old processes still running code from module ~p",
|
||
[Vsn, Mod]),
|
||
erlang:halt(3);
|
||
{error, Reason1} ->
|
||
?INFO("ERROR: release_handler:install_release failed: ~p",[Reason1]),
|
||
erlang:halt(4)
|
||
end.
|
||
|
||
maybe_permafy(TargetNode, RelName, Vsn, Opts) ->
|
||
case proplists:get_value(permanent, Opts, true) of
|
||
true ->
|
||
permafy(TargetNode, RelName, Vsn);
|
||
false -> ok
|
||
end.
|
||
|
||
permafy(TargetNode, RelName, Vsn) ->
|
||
RelNameStr = atom_to_list(RelName),
|
||
ok = rpc:call(TargetNode, release_handler,
|
||
make_permanent, [Vsn], ?TIMEOUT),
|
||
?INFO("Made release permanent: ~p", [Vsn]),
|
||
%% upgrade/downgrade the scripts by replacing them
|
||
Scripts = [RelNameStr, RelNameStr++"_ctl", "cuttlefish", "nodetool",
|
||
"install_upgrade.escript"],
|
||
[{ok, _} = file:copy(filename:join(["bin", File++"-"++Vsn]),
|
||
filename:join(["bin", File]))
|
||
|| File <- Scripts],
|
||
%% update the vars
|
||
UpdatedVars = io_lib:format("REL_VSN=\"~s\"~nERTS_VSN=\"~s\"~n", [Vsn, erts_vsn()]),
|
||
file:write_file(filename:absname(filename:join(["releases", "emqx_vars"])), UpdatedVars, [append]).
|
||
|
||
remove_release(TargetNode, Vsn) ->
|
||
case rpc:call(TargetNode, release_handler, remove_release, [Vsn], ?TIMEOUT) of
|
||
ok ->
|
||
?INFO("Uninstalled Release: ~s", [Vsn]),
|
||
ok;
|
||
{error, Reason} ->
|
||
?INFO("ERROR: release_handler:remove_release failed: ~p", [Reason]),
|
||
erlang:halt(3)
|
||
end.
|
||
|
||
which_releases(TargetNode) ->
|
||
R = rpc:call(TargetNode, release_handler, which_releases, [], ?TIMEOUT),
|
||
[ {V, S} || {_,V,_, S} <- R ].
|
||
|
||
%% the running release version is either the only one marked `current´
|
||
%% or, if none exists, the one marked `permanent`
|
||
current_release_version(TargetNode) ->
|
||
R = rpc:call(TargetNode, release_handler, which_releases,
|
||
[], ?TIMEOUT),
|
||
Versions = [ {S, V} || {_,V,_, S} <- R ],
|
||
%% current version takes priority over the permanent
|
||
proplists:get_value(current, Versions,
|
||
proplists:get_value(permanent, Versions)).
|
||
|
||
print_existing_versions(TargetNode) ->
|
||
VerList = iolist_to_binary([
|
||
io_lib:format("* ~s\t~s~n",[V,S])
|
||
|| {V,S} <- which_releases(TargetNode) ]),
|
||
?INFO("Installed versions:~n~s", [VerList]).
|
||
|
||
start_distribution(TargetNode, NameTypeArg, Cookie) ->
|
||
MyNode = make_script_node(TargetNode),
|
||
{ok, _Pid} = net_kernel:start([MyNode, get_name_type(NameTypeArg)]),
|
||
erlang:set_cookie(node(), Cookie),
|
||
case {net_kernel:connect_node(TargetNode),
|
||
net_adm:ping(TargetNode)} of
|
||
{true, pong} ->
|
||
ok;
|
||
{_, pang} ->
|
||
?INFO("Node ~p not responding to pings.", [TargetNode]),
|
||
erlang:halt(1)
|
||
end,
|
||
{ok, Cwd} = file:get_cwd(),
|
||
ok = rpc:call(TargetNode, file, set_cwd, [Cwd], ?TIMEOUT),
|
||
TargetNode.
|
||
|
||
make_script_node(Node) ->
|
||
[Name, Host] = string:tokens(atom_to_list(Node), "@"),
|
||
list_to_atom(lists:concat([Name, "_upgrader_", os:getpid(), "@", Host])).
|
||
|
||
%% get name type from arg
|
||
get_name_type(NameTypeArg) ->
|
||
case NameTypeArg of
|
||
"-sname" ->
|
||
shortnames;
|
||
_ ->
|
||
longnames
|
||
end.
|
||
|
||
erts_vsn() ->
|
||
{ok, Str} = file:read_file(filename:join(["releases", "start_erl.data"])),
|
||
[ErtsVsn, _] = string:tokens(binary_to_list(Str), " "),
|
||
ErtsVsn.
|