messy
This commit is contained in:
parent
fafea57954
commit
34656bef64
433
zx
433
zx
@ -21,6 +21,10 @@
|
|||||||
{realm = "otpr" :: name(),
|
{realm = "otpr" :: name(),
|
||||||
name = none :: none | name(),
|
name = none :: none | name(),
|
||||||
version = {z, z, z} :: version(),
|
version = {z, z, z} :: version(),
|
||||||
|
type = app :: app | lib
|
||||||
|
deps = [] :: [package_id()],
|
||||||
|
dir = none :: none | file:filename(),
|
||||||
|
socket = none :: none | gen_tcp:socket(),
|
||||||
pid = none :: none | pid(),
|
pid = none :: none | pid(),
|
||||||
mon = none :: none | reference()}).
|
mon = none :: none | reference()}).
|
||||||
|
|
||||||
@ -39,7 +43,8 @@
|
|||||||
%-type keybin() :: {ID :: key_id(),
|
%-type keybin() :: {ID :: key_id(),
|
||||||
% Type :: public | private,
|
% Type :: public | private,
|
||||||
% DER :: binary()}.
|
% DER :: binary()}.
|
||||||
-type key_id() :: {realm(), KeyName :: lower0_9()}.
|
-type key_id() :: {realm(), key_name()}.
|
||||||
|
-type key_name() :: lower0_9().
|
||||||
-type lower0_9() :: [$a..$z | $0..$9 | $_].
|
-type lower0_9() :: [$a..$z | $0..$9 | $_].
|
||||||
%-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
%-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
||||||
|
|
||||||
@ -64,7 +69,7 @@ main(Args) ->
|
|||||||
start(["help"]) ->
|
start(["help"]) ->
|
||||||
usage_exit(0);
|
usage_exit(0);
|
||||||
start(["run", PackageString | Args]) ->
|
start(["run", PackageString | Args]) ->
|
||||||
execute(PackageString, Args);
|
run(PackageString, Args);
|
||||||
start(["init", "app", PackageString]) ->
|
start(["init", "app", PackageString]) ->
|
||||||
PackageID = package_id(PackageString),
|
PackageID = package_id(PackageString),
|
||||||
initialize(app, PackageID);
|
initialize(app, PackageID);
|
||||||
@ -84,9 +89,7 @@ start(["drop", "key", KeyID]) ->
|
|||||||
drop_key(KeyID);
|
drop_key(KeyID);
|
||||||
start(["verup", Level]) ->
|
start(["verup", Level]) ->
|
||||||
verup(Level);
|
verup(Level);
|
||||||
start(["runlocal"]) ->
|
start(["runlocal" | Args]) ->
|
||||||
run_local([]);
|
|
||||||
start(["runlocal", Args]) ->
|
|
||||||
run_local(Args);
|
run_local(Args);
|
||||||
start(["package"]) ->
|
start(["package"]) ->
|
||||||
{ok, TargetDir} = file:get_cwd(),
|
{ok, TargetDir} = file:get_cwd(),
|
||||||
@ -115,7 +118,7 @@ start(_) ->
|
|||||||
%%% Execution of application
|
%%% Execution of application
|
||||||
|
|
||||||
|
|
||||||
-spec execute(Identifier, Args) -> no_return()
|
-spec run(Identifier, Args) -> no_return()
|
||||||
when Identifier :: string(),
|
when Identifier :: string(),
|
||||||
Args :: [string()].
|
Args :: [string()].
|
||||||
%% @private
|
%% @private
|
||||||
@ -123,7 +126,7 @@ start(_) ->
|
|||||||
%% dependencies and run the program. This implies determining whether the program and
|
%% dependencies and run the program. This implies determining whether the program and
|
||||||
%% its dependencies are installed, available, need to be downloaded, or are inaccessible
|
%% its dependencies are installed, available, need to be downloaded, or are inaccessible
|
||||||
%% given the current system condition (they could also be bogus, of course). The
|
%% given the current system condition (they could also be bogus, of course). The
|
||||||
%% Identifier provided should be a valid PackageString of the form `realm-appname-version'
|
%% Identifier should be a valid PackageString of the form `realm-appname-version'
|
||||||
%% where the realm and appname should follow standard realm and app package naming
|
%% where the realm and appname should follow standard realm and app package naming
|
||||||
%% conventions and the version should be represented as a semver in string form (where
|
%% conventions and the version should be represented as a semver in string form (where
|
||||||
%% ommitted elements of the version always default to whatever is most current).
|
%% ommitted elements of the version always default to whatever is most current).
|
||||||
@ -135,15 +138,21 @@ start(_) ->
|
|||||||
%% If there is a problem anywhere in the locationg, discovery, building, and loading
|
%% If there is a problem anywhere in the locationg, discovery, building, and loading
|
||||||
%% procedure the runtime will halt with an error message.
|
%% procedure the runtime will halt with an error message.
|
||||||
|
|
||||||
execute(Identifier, Args) ->
|
run(Identifier, Args) ->
|
||||||
true = register(zx, self()),
|
|
||||||
ok = inets:start(),
|
|
||||||
PackageID = {Realm, Name, Version} = package_id(Identifier),
|
PackageID = {Realm, Name, Version} = package_id(Identifier),
|
||||||
ok = file:set_cwd(zomp_dir()),
|
ok = file:set_cwd(zomp_dir()),
|
||||||
PackageRoot = filename:join("lib", Identifier),
|
PackageRoot = filename:join("lib", Identifier),
|
||||||
ok = ensure_installed(PackageID),
|
State = #s{realm = Realm,
|
||||||
{ok, Meta} = file:consult(filename:join(PackageRoot, "zomp.meta")),
|
name = Name,
|
||||||
{deps, Deps} = lists:keyfind(deps, 1, Meta),
|
version = Version,
|
||||||
|
dir = PackageRoot},
|
||||||
|
NextState = ensure_installed(State),
|
||||||
|
Meta = read_meta(PackageRoot),
|
||||||
|
Deps = maps:get(deps, Meta),
|
||||||
|
NewState = ensure_deps(NextState#s{deps = Deps}),
|
||||||
|
execute(State).
|
||||||
|
|
||||||
|
|
||||||
Required = [PackageID | Deps],
|
Required = [PackageID | Deps],
|
||||||
Needed = scrub(Required),
|
Needed = scrub(Required),
|
||||||
Host = {"localhost", 11411},
|
Host = {"localhost", 11411},
|
||||||
@ -152,8 +161,10 @@ execute(Identifier, Args) ->
|
|||||||
ok = lists:foreach(fun install/1, Needed),
|
ok = lists:foreach(fun install/1, Needed),
|
||||||
ok = lists:foreach(fun build/1, Required),
|
ok = lists:foreach(fun build/1, Required),
|
||||||
ok = file:set_cwd(PackageRoot),
|
ok = file:set_cwd(PackageRoot),
|
||||||
case lists:keyfind(type, 1, Meta) of
|
case maps:get(type, Meta) of
|
||||||
{type, app} ->
|
app ->
|
||||||
|
true = register(zx, self()),
|
||||||
|
ok = inets:start(),
|
||||||
ok = log(info, "Starting ~ts", [package_string(PackageID)]),
|
ok = log(info, "Starting ~ts", [package_string(PackageID)]),
|
||||||
PackageMod = list_to_atom(Name),
|
PackageMod = list_to_atom(Name),
|
||||||
{ok, Pid} = PackageMod:start(normal, Args),
|
{ok, Pid} = PackageMod:start(normal, Args),
|
||||||
@ -166,7 +177,7 @@ execute(Identifier, Args) ->
|
|||||||
pid = Pid,
|
pid = Pid,
|
||||||
mon = Mon},
|
mon = Mon},
|
||||||
exec_wait(State);
|
exec_wait(State);
|
||||||
{type, lib} ->
|
lib ->
|
||||||
Message = "Lib ~ts is available on the system, but is not a standalone app.",
|
Message = "Lib ~ts is available on the system, but is not a standalone app.",
|
||||||
ok = log(info, Message, [package_string(PackageID)]),
|
ok = log(info, Message, [package_string(PackageID)]),
|
||||||
halt(0)
|
halt(0)
|
||||||
@ -195,7 +206,7 @@ initialize(Type, PackageID) ->
|
|||||||
Meta = [{package_id, PackageID},
|
Meta = [{package_id, PackageID},
|
||||||
{deps, []},
|
{deps, []},
|
||||||
{type, Type}],
|
{type, Type}],
|
||||||
ok = write_terms("zomp.meta", Meta),
|
ok = write_meta(Meta),
|
||||||
ok = log(info, "Project ~tp initialized.", [PackageString]),
|
ok = log(info, "Project ~tp initialized.", [PackageString]),
|
||||||
Message =
|
Message =
|
||||||
"NOTICE:~n"
|
"NOTICE:~n"
|
||||||
@ -224,10 +235,10 @@ assimilate(PackageFile) ->
|
|||||||
ok = file:set_cwd(zomp_dir()),
|
ok = file:set_cwd(zomp_dir()),
|
||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
||||||
Meta = binary_to_term(MetaBin),
|
Meta = binary_to_term(MetaBin),
|
||||||
{package_id, PackageID} = lists:keyfind(package_id, 1, Meta),
|
PackageID = maps:get(package_id, Meta),
|
||||||
TgzFile = namify_tgz(PackageID),
|
TgzFile = namify_tgz(PackageID),
|
||||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
||||||
{sig, {KeyID, Signature}} = lists:keyfind(sig, 1, Meta),
|
{KeyID, Signature} = maps:get(sig, Meta),
|
||||||
{ok, PubKey} = loadkey(public, KeyID),
|
{ok, PubKey} = loadkey(public, KeyID),
|
||||||
ok =
|
ok =
|
||||||
case public_key:verify(TgzData, sha512, Signature, PubKey) of
|
case public_key:verify(TgzData, sha512, Signature, PubKey) of
|
||||||
@ -259,7 +270,7 @@ assimilate(PackageFile) ->
|
|||||||
set_dep(PackageString) ->
|
set_dep(PackageString) ->
|
||||||
PackageID = package_id(PackageString),
|
PackageID = package_id(PackageString),
|
||||||
Meta = read_meta(),
|
Meta = read_meta(),
|
||||||
{deps, Deps} = lists:keyfind(deps, 1, Meta),
|
Deps = maps:get(deps, Meta),
|
||||||
case lists:member(PackageID, Deps) of
|
case lists:member(PackageID, Deps) of
|
||||||
true ->
|
true ->
|
||||||
ok = log(info, "~ts is already a dependency", [PackageString]),
|
ok = log(info, "~ts is already a dependency", [PackageString]),
|
||||||
@ -279,11 +290,9 @@ set_dep(PackageString) ->
|
|||||||
%% such a dependency is not already present. Then write the project meta back to its
|
%% such a dependency is not already present. Then write the project meta back to its
|
||||||
%% file and exit.
|
%% file and exit.
|
||||||
|
|
||||||
set_dep(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
|
set_dep(PackageID = {Realm, Name, Version}, Deps, Meta) ->
|
||||||
{ok, CWD} = file:get_cwd(),
|
{ok, CWD} = file:get_cwd(),
|
||||||
ok = file:set_cwd(zomp_dir()),
|
LatestVersion = latest_version(PackageID),
|
||||||
ok = ensure_installed(PackageID),
|
|
||||||
ok = file:set_cwd(CWD),
|
|
||||||
ExistingPackageIDs = fun ({R, N, _}) -> {R, N} == {Realm, Name} end,
|
ExistingPackageIDs = fun ({R, N, _}) -> {R, N} == {Realm, Name} end,
|
||||||
NewDeps =
|
NewDeps =
|
||||||
case lists:partition(ExistingPackageIDs, Deps) of
|
case lists:partition(ExistingPackageIDs, Deps) of
|
||||||
@ -297,18 +306,37 @@ set_dep(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
|
|||||||
ok = log(info, "Adding dep ~ts", [package_string(PackageID)]),
|
ok = log(info, "Adding dep ~ts", [package_string(PackageID)]),
|
||||||
[PackageID | Deps]
|
[PackageID | Deps]
|
||||||
end,
|
end,
|
||||||
NewMeta = lists:keystore(deps, 1, Meta, {deps, NewDeps}),
|
NewMeta = maps:put(deps, NewDeps, Meta),
|
||||||
ok = write_terms("zomp.meta", NewMeta),
|
ok = write_meta(NewMeta),
|
||||||
halt(0).
|
halt(0).
|
||||||
|
|
||||||
|
|
||||||
-spec ensure_installed(package_id()) -> ok | no_return().
|
-spec latest_version(package_id()) -> version().
|
||||||
|
%% @private
|
||||||
|
%% Query the relevant realm for the latest version of a given package.
|
||||||
|
|
||||||
|
latest_version({Realm, Name, Version}) ->
|
||||||
|
Socket = connect(Realm),
|
||||||
|
ok = send(Socket, {latest, Realm, Name, Version}),
|
||||||
|
Response =
|
||||||
|
receive
|
||||||
|
{tcp, Socket, Bin} -> binary_to_term(Bin, [safe])
|
||||||
|
after 5000 -> {error, timeout}
|
||||||
|
end,
|
||||||
|
ok = disconnect(Socket).
|
||||||
|
|
||||||
|
|
||||||
|
-spec ensure_installed(State) -> NewState | no_return()
|
||||||
|
when State :: state(),
|
||||||
|
NewState :: state().
|
||||||
%% @private
|
%% @private
|
||||||
%% Given a PackageID, check whether it is installed on the system, and if not, ensure
|
%% Given a PackageID, check whether it is installed on the system, and if not, ensure
|
||||||
%% that the package is either in the cache or can be downloaded. If all attempts at
|
%% that the package is either in the cache or can be downloaded. If all attempts at
|
||||||
%% locating or acquiring the package fail, then exit with an error.
|
%% locating or acquiring the package fail, then exit with an error.
|
||||||
|
|
||||||
ensure_installed(PackageID) ->
|
ensure_installed(State = #s{realm = Realm, name = Name, version = Version}) ->
|
||||||
|
% If the startup style is to always check first, match for the exact version,
|
||||||
|
% If the startup style is to run a matching latest and then check
|
||||||
PackageString = package_string(PackageID),
|
PackageString = package_string(PackageID),
|
||||||
PackageDir = filename:join("lib", PackageString),
|
PackageDir = filename:join("lib", PackageString),
|
||||||
case filelib:is_dir(PackageDir) of
|
case filelib:is_dir(PackageDir) of
|
||||||
@ -421,7 +449,7 @@ update_version(NewVersion, {Realm, Name, OldVersion}, OldMeta) ->
|
|||||||
update_version(Realm, Name, OldVersion, NewVersion, OldMeta) ->
|
update_version(Realm, Name, OldVersion, NewVersion, OldMeta) ->
|
||||||
PackageID = {Realm, Name, NewVersion},
|
PackageID = {Realm, Name, NewVersion},
|
||||||
NewMeta = lists:keystore(package_id, 1, OldMeta, {package_id, PackageID}),
|
NewMeta = lists:keystore(package_id, 1, OldMeta, {package_id, PackageID}),
|
||||||
ok = write_terms("zomp.meta", NewMeta),
|
ok = write_meta(NewMeta),
|
||||||
ok = log(info,
|
ok = log(info,
|
||||||
"Version changed from ~s to ~s.",
|
"Version changed from ~s to ~s.",
|
||||||
[version_to_string(OldVersion), version_to_string(NewVersion)]),
|
[version_to_string(OldVersion), version_to_string(NewVersion)]),
|
||||||
@ -444,7 +472,7 @@ drop_dep(PackageID) ->
|
|||||||
true ->
|
true ->
|
||||||
NewDeps = lists:delete(PackageID, Deps),
|
NewDeps = lists:delete(PackageID, Deps),
|
||||||
NewMeta = lists:keystore(deps, 1, Meta, {deps, NewDeps}),
|
NewMeta = lists:keystore(deps, 1, Meta, {deps, NewDeps}),
|
||||||
ok = write_terms("zomp.meta", NewMeta),
|
ok = write_meta(NewMeta),
|
||||||
Message = "~ts removed from dependencies.",
|
Message = "~ts removed from dependencies.",
|
||||||
ok = log(info, Message, [PackageString]),
|
ok = log(info, Message, [PackageString]),
|
||||||
halt(0);
|
halt(0);
|
||||||
@ -508,15 +536,51 @@ verup(_) -> usage_exit(22).
|
|||||||
%% and use zx commands to add or drop dependencies made available via zomp.
|
%% and use zx commands to add or drop dependencies made available via zomp.
|
||||||
|
|
||||||
run_local(Args) ->
|
run_local(Args) ->
|
||||||
true = register(zx, self()),
|
|
||||||
ok = inets:start(),
|
|
||||||
{ok, ProjectRoot} = file:get_cwd(),
|
|
||||||
Meta = read_meta(),
|
Meta = read_meta(),
|
||||||
{package_id, PackageID} = lists:keyfind(package_id, 1, Meta),
|
{package_id, PackageID} = lists:keyfind(package_id, 1, Meta),
|
||||||
{Realm, Name, Version} = PackageID,
|
{Realm, Name, Version} = PackageID,
|
||||||
ok = build(),
|
{type, Type} = lists:keyfind(type, 1, Meta),
|
||||||
ok = file:set_cwd(zomp_dir()),
|
|
||||||
{deps, Deps} = lists:keyfind(deps, 1, Meta),
|
{deps, Deps} = lists:keyfind(deps, 1, Meta),
|
||||||
|
ok = build(),
|
||||||
|
{ok, Dir} = file:get_cwd(),
|
||||||
|
ok = file:set_cwd(zomp_dir()),
|
||||||
|
State = #s{realm = Realm,
|
||||||
|
name = Name,
|
||||||
|
version = Version,
|
||||||
|
type = Type,
|
||||||
|
deps = Deps,
|
||||||
|
dir = Dir},
|
||||||
|
NewState = ensure_deps(State),
|
||||||
|
execute(State).
|
||||||
|
|
||||||
|
|
||||||
|
ensure_deps(Deps, State) ->
|
||||||
|
|
||||||
|
execute(State).
|
||||||
|
|
||||||
|
|
||||||
|
execute(State = #s{type = app}) ->
|
||||||
|
true = register(zx, self()),
|
||||||
|
ok = inets:start(),
|
||||||
|
ok = log(info, "Starting ~ts", [package_string(PackageID)]),
|
||||||
|
AppMod = list_to_atom(Name),
|
||||||
|
{ok, Pid} = AppMod:start(normal, Args),
|
||||||
|
Mon = monitor(process, Pid),
|
||||||
|
Shell = spawn(shell, start, []),
|
||||||
|
ok = log(info, "Your shell is ~p, application is: ~p", [Shell, Pid]),
|
||||||
|
State = #s{realm = Realm,
|
||||||
|
name = Name,
|
||||||
|
version = Version,
|
||||||
|
pid = Pid,
|
||||||
|
mon = Mon},
|
||||||
|
exec_wait(State);
|
||||||
|
execute(State = #s{type = lib}) ->
|
||||||
|
Message = "Lib ~ts is available on the system, but is not a standalone app",
|
||||||
|
ok = log(info, Message, [package_string(PackageID)]),
|
||||||
|
halt(0).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Needed = scrub(Deps),
|
Needed = scrub(Deps),
|
||||||
Host = {"localhost", 11411},
|
Host = {"localhost", 11411},
|
||||||
Socket = connect(Host, user),
|
Socket = connect(Host, user),
|
||||||
@ -524,25 +588,6 @@ run_local(Args) ->
|
|||||||
ok = lists:foreach(fun install/1, Needed),
|
ok = lists:foreach(fun install/1, Needed),
|
||||||
ok = lists:foreach(fun build/1, Deps),
|
ok = lists:foreach(fun build/1, Deps),
|
||||||
ok = file:set_cwd(ProjectRoot),
|
ok = file:set_cwd(ProjectRoot),
|
||||||
case lists:keyfind(type, 1, Meta) of
|
|
||||||
{type, app} ->
|
|
||||||
ok = log(info, "Starting ~ts", [package_string(PackageID)]),
|
|
||||||
AppMod = list_to_atom(Name),
|
|
||||||
{ok, Pid} = AppMod:start(normal, Args),
|
|
||||||
Mon = monitor(process, Pid),
|
|
||||||
Shell = spawn(shell, start, []),
|
|
||||||
ok = log(info, "Your shell is ~p, application is: ~p", [Shell, Pid]),
|
|
||||||
State = #s{realm = Realm,
|
|
||||||
name = Name,
|
|
||||||
version = Version,
|
|
||||||
pid = Pid,
|
|
||||||
mon = Mon},
|
|
||||||
exec_wait(State);
|
|
||||||
{type, lib} ->
|
|
||||||
Message = "Lib ~ts is available on the system, but is not a standalone app",
|
|
||||||
ok = log(info, Message, [package_string(PackageID)]),
|
|
||||||
halt(0)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -556,7 +601,7 @@ run_local(Args) ->
|
|||||||
|
|
||||||
package(TargetDir) ->
|
package(TargetDir) ->
|
||||||
ok = log(info, "Packaging ~ts", [TargetDir]),
|
ok = log(info, "Packaging ~ts", [TargetDir]),
|
||||||
{ok, Meta} = file:consult(filename:join(TargetDir, "zomp.meta")),
|
Meta = read_meta(TargetDir),
|
||||||
{package_id, {Realm, _, _}} = lists:keyfind(package_id, 1, Meta),
|
{package_id, {Realm, _, _}} = lists:keyfind(package_id, 1, Meta),
|
||||||
KeyDir = filename:join([zomp_dir(), "key", Realm]),
|
KeyDir = filename:join([zomp_dir(), "key", Realm]),
|
||||||
ok = force_dir(KeyDir),
|
ok = force_dir(KeyDir),
|
||||||
@ -585,7 +630,7 @@ package(TargetDir) ->
|
|||||||
%% build a zrp package file ready to be submitted to a repository.
|
%% build a zrp package file ready to be submitted to a repository.
|
||||||
|
|
||||||
package(KeyID, TargetDir) ->
|
package(KeyID, TargetDir) ->
|
||||||
{ok, Meta} = file:consult(filename:join(TargetDir, "zomp.meta")),
|
Meta = read_meta(TargetDir),
|
||||||
{package_id, PackageID} = lists:keyfind(package_id, 1, Meta),
|
{package_id, PackageID} = lists:keyfind(package_id, 1, Meta),
|
||||||
true = element(1, PackageID) == element(1, KeyID),
|
true = element(1, PackageID) == element(1, KeyID),
|
||||||
PackageString = package_string(PackageID),
|
PackageString = package_string(PackageID),
|
||||||
@ -704,11 +749,9 @@ submit(PackageFile) ->
|
|||||||
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
{"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files),
|
||||||
Meta = binary_to_term(MetaBin),
|
Meta = binary_to_term(MetaBin),
|
||||||
{package_id, {Realm, Package, Version}} = lists:keyfind(package_id, 1, Meta),
|
{package_id, {Realm, Package, Version}} = lists:keyfind(package_id, 1, Meta),
|
||||||
{sig, {KeyID, _}} = lists:keyfind(sig, 1, Meta),
|
{sig, {KeyID = {Realm, KeyName}, _}} = lists:keyfind(sig, 1, Meta),
|
||||||
true = ensure_keypair(KeyID),
|
true = ensure_keypair(KeyID),
|
||||||
RealmData = realm_data(Realm),
|
{ok, Socket = connect_auth(Realm, KeyName),
|
||||||
{prime, Prime} = lists:keyfind(prime, 1, RealmData),
|
|
||||||
Socket = connect(Prime, {auth, KeyID}),
|
|
||||||
ok = send(Socket, {submit, {Realm, Package, Version}}),
|
ok = send(Socket, {submit, {Realm, Package, Version}}),
|
||||||
ok =
|
ok =
|
||||||
receive
|
receive
|
||||||
@ -730,8 +773,6 @@ submit(PackageFile) ->
|
|||||||
receive
|
receive
|
||||||
{tcp, Socket, Response2} ->
|
{tcp, Socket, Response2} ->
|
||||||
log(info, "Response: ~tp", [Response2]);
|
log(info, "Response: ~tp", [Response2]);
|
||||||
Other ->
|
|
||||||
log(warning, "Unexpected message: ~tp", [Other])
|
|
||||||
after 5000 ->
|
after 5000 ->
|
||||||
log(warning, "Server timed out!")
|
log(warning, "Server timed out!")
|
||||||
end,
|
end,
|
||||||
@ -750,67 +791,196 @@ send(Socket, Message) ->
|
|||||||
gen_tcp:send(Socket, Bin).
|
gen_tcp:send(Socket, Bin).
|
||||||
|
|
||||||
|
|
||||||
-spec connect(Node, Type) -> gen_tcp:socket() | no_return()
|
-spec connect_user(realm()) -> gen_tcp:socket() | no_return().
|
||||||
when Node :: host(),
|
|
||||||
Type :: user | {auth, key_id()}.
|
|
||||||
%% @private
|
%% @private
|
||||||
%% Connect to one of the servers in the realm constellation.
|
%% Connect to a given realm, whatever method is required.
|
||||||
|
|
||||||
connect({Host, Port}, Type) ->
|
connect_user(Realm) ->
|
||||||
Options = [{packet, 4}, {mode, binary}, {active, true}],
|
Hosts =
|
||||||
case gen_tcp:connect(Host, Port, Options) of
|
case file:consult(hosts_cache_file(Realm)) of
|
||||||
|
{ok, Cached} -> Cached;
|
||||||
|
{error, enoent} -> []
|
||||||
|
end,
|
||||||
|
connect_user(Realm, Hosts).
|
||||||
|
|
||||||
|
|
||||||
|
-spec connect_user(realm(), [host()]) -> gen_tcp:socket() | no_return().
|
||||||
|
%% @private
|
||||||
|
%% Try to connect to a subordinate host, if there are none then connect to prime.
|
||||||
|
|
||||||
|
connect_user(Realm, []) ->
|
||||||
|
{Host, Port} = get_prime(Realm),
|
||||||
|
case gen_tcp:connect(Host, Port, connect_options(), 5000) of
|
||||||
{ok, Socket} ->
|
{ok, Socket} ->
|
||||||
confirm_server(Socket, Type);
|
confirm_user(Realm, Socket, []);
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
ok = log(warning, "Connection problem: ~tp", [Error]),
|
ok = log(warning, "Connection problem with prime: ~tp", [Error]),
|
||||||
halt(0)
|
halt(0)
|
||||||
|
end;
|
||||||
|
connect_user(Realm, Hosts = [Node = {Host, Port} | Rest]) ->
|
||||||
|
case gen_tcp:connect(Host, Port, connect_options(), 5000) of
|
||||||
|
{ok, Socket} ->
|
||||||
|
confirm_user(Realm, Socket, Hosts);
|
||||||
|
{error, Error} ->
|
||||||
|
ok = log(warning, "Connection problem with ~tp: ~tp", [Node, Error]),
|
||||||
|
connect_user(Realm, Rest)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec confirm_server(Socket, Type) -> gen_tcp:socket() | no_return()
|
-spec confirm_user(Realm, Socket, Hosts) -> Socket | no_return()
|
||||||
when Socket :: gen_tcp:socket(),
|
when Realm :: realm(),
|
||||||
Type :: user | {auth, key_id()}.
|
Socket :: gen_tcp:socket(),
|
||||||
|
Hosts :: [host()].
|
||||||
%% @private
|
%% @private
|
||||||
%% Send a protocol ID string to notify the server what we're up to, disconnect
|
%% Confirm the zomp node can handle "OTPR USER 1" and is accepting connections or try another node.
|
||||||
%% if it does not return an "OK" response within 5 seconds.
|
|
||||||
|
|
||||||
confirm_server(Socket, user) ->
|
confirm_user(Realm, Socket, Hosts) ->
|
||||||
{ok, {Addr, Port}} = inet:peername(Socket),
|
{ok, {Addr, Port}} = inet:peername(Socket),
|
||||||
Host = inet:ntoa(Addr),
|
Host = inet:ntoa(Addr),
|
||||||
ok = gen_tcp:send(Socket, <<"OTPR USER 1">>),
|
ok = gen_tcp:send(Socket, <<"OTPR USER 1">>),
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, <<"OK">>} ->
|
{tcp, Socket, Bin} ->
|
||||||
ok = log(info, "Connected to ~s:~p", [Host, Port]),
|
case binary_to_term(Bin, [safe]) of
|
||||||
Socket;
|
ok ->
|
||||||
Other ->
|
ok = log(info, "Connected to ~s:~p", [Host, Port]),
|
||||||
Message = "Unexpected response from ~s:~p:~n~tp",
|
confirm_serial(Realm, Socket, Hosts, user);
|
||||||
ok = log(warning, Message, [Host, Port, Other]),
|
{redirect, Next} ->
|
||||||
ok = disconnect(Socket),
|
ok = log(info, "Redirected..."),
|
||||||
halt(0)
|
ok = disconnect(Socket),
|
||||||
|
connect_user(Realm, Next ++ Hosts)
|
||||||
|
end
|
||||||
after 5000 ->
|
after 5000 ->
|
||||||
ok = log(warning, "Host ~s:~p timed out.", [Host, Port]),
|
ok = log(warning, "Host ~s:~p timed out.", [Host, Port]),
|
||||||
halt(0)
|
ok = disconnect(Socket),
|
||||||
end;
|
connect_user(Realm, tl(Hosts))
|
||||||
confirm_server(Socket, {auth, KeyID}) ->
|
end.
|
||||||
ok = log(info, "Would now be trying to connect as AUTH using ~tp", [KeyID]),
|
|
||||||
|
|
||||||
|
-spec confirm_serial(Realm, Socket, Hosts) -> Socket | no_return()
|
||||||
|
when Realm :: realm(),
|
||||||
|
Socket :: gen_tcp:socket(),
|
||||||
|
Hosts :: [host()].
|
||||||
|
%% @private
|
||||||
|
%% Confirm that the connected host has a valid serial for the realm zx is trying to
|
||||||
|
%% reach, and if not retry on another node.
|
||||||
|
|
||||||
|
confirm_serial(Realm, Socket, Hosts) ->
|
||||||
|
SerialFile = filename:join(zomp_dir(), "realm.serials"),
|
||||||
|
Serials =
|
||||||
|
case file:consult(SerialFile) of
|
||||||
|
{ok, Serials} -> Serials;
|
||||||
|
{error, enoent} -> []
|
||||||
|
end,
|
||||||
|
Serial =
|
||||||
|
case lists:keyfind(Realm, 1, Serials) of
|
||||||
|
false -> 1;
|
||||||
|
{Realm, S} -> S
|
||||||
|
end,
|
||||||
|
ok = send(Socket, {latest, Realm}),
|
||||||
|
receive
|
||||||
|
{tcp, Socket, Bin} ->
|
||||||
|
case binary_to_term(Bin, [safe]) of
|
||||||
|
{ok, Serial} ->
|
||||||
|
ok = log(info, "Node's serial same as ours."),
|
||||||
|
Socket;
|
||||||
|
{ok, Current} when Current > Serial ->
|
||||||
|
ok = log(info, "Node's serial newer than ours. Storing."),
|
||||||
|
NewSerials = lists:keystore(Realm, 1, Current, Serials),
|
||||||
|
ok = write_terms(hosts_cache_file(Realm), Hosts),
|
||||||
|
ok = write_terms(SerialFile, NewSerials),
|
||||||
|
Socket;
|
||||||
|
{ok, Current} when Current < Serial ->
|
||||||
|
ok = log(info, "Node's serial older than ours. Trying another."),
|
||||||
|
ok = disconnect(Socket),
|
||||||
|
connect_user(Realm, tl(Hosts));
|
||||||
|
{error, bad_realm} ->
|
||||||
|
ok = log(info, "Node is no longer serving realm. Trying another."),
|
||||||
|
ok = disconnect(Socket),
|
||||||
|
connect_user(Realm, tl(Hosts))
|
||||||
|
end
|
||||||
|
after 5000 ->
|
||||||
|
ok = log(info, "Host timed out on confirm_serial. Trying another."),
|
||||||
|
ok = disconnect(Socket),
|
||||||
|
connect_user(Realm, tl(Hosts))
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec connect_auth(Realm, KeyName) -> Result
|
||||||
|
when Realm :: realm(),
|
||||||
|
KeyName :: key_name(),
|
||||||
|
Result :: {ok, gen_tcp:socket()}
|
||||||
|
| {error, Reason :: term()}.
|
||||||
|
%% @private
|
||||||
|
%% Connect to one of the servers in the realm constellation.
|
||||||
|
|
||||||
|
connect_auth(Realm, KeyName) ->
|
||||||
|
{ok, Key} = loadkey(private, {Realm, KeyName}),
|
||||||
|
{Host, Port} = get_prime(Realm),
|
||||||
|
case gen_tcp:connect(Host, Port, connect_options(), 5000) of
|
||||||
|
{ok, Socket} ->
|
||||||
|
ok = log(info, "Connected to ~tp prime.", [Realm]),
|
||||||
|
confirm_auth(Socket, Key);
|
||||||
|
Error = {error, E} ->
|
||||||
|
ok = log(warning, "Connection problem: ~tp", [E]),
|
||||||
|
{error, Error}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-spec confirm_auth(Socket, Key) -> Result
|
||||||
|
when Socket :: gen_tcp:socket(),
|
||||||
|
Key :: term(),
|
||||||
|
Result :: {ok, gen_tcp:socket()}
|
||||||
|
| {error, timeout}.
|
||||||
|
%% @private
|
||||||
|
%% Send a protocol ID string to notify the server what we're up to, disconnect
|
||||||
|
%% if it does not return an "OK" response within 5 seconds.
|
||||||
|
|
||||||
|
confirm_auth(Socket, Key) ->
|
||||||
|
ok = log(info, "Would be using key ~tp now", [Key]),
|
||||||
{ok, {Addr, Port}} = inet:peername(Socket),
|
{ok, {Addr, Port}} = inet:peername(Socket),
|
||||||
Host = inet:ntoa(Addr),
|
|
||||||
ok = gen_tcp:send(Socket, <<"OTPR AUTH 1">>),
|
ok = gen_tcp:send(Socket, <<"OTPR AUTH 1">>),
|
||||||
receive
|
receive
|
||||||
{tcp, Socket, <<"OK">>} ->
|
{tcp, Socket, <<"OK">>} ->
|
||||||
ok = log(info, "Connected to ~s:~p", [Host, Port]),
|
{ok, Socket}
|
||||||
Socket;
|
|
||||||
Other ->
|
|
||||||
Message = "Unexpected response from ~s:~p:~n~tp",
|
|
||||||
ok = log(warning, Message, [Host, Port, Other]),
|
|
||||||
ok = disconnect(Socket),
|
|
||||||
halt(0)
|
|
||||||
after 5000 ->
|
after 5000 ->
|
||||||
ok = log(warning, "Host ~s:~p timed out.", [Host, Port]),
|
ok = log(warning, "Host ~s:~p timed out.", [Host, Port]),
|
||||||
halt(0)
|
{error, auth_timeout}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec connect_options() -> [gen_tcp:connect_option()].
|
||||||
|
%% @private
|
||||||
|
%% Hide away the default options used for TCP connections.
|
||||||
|
|
||||||
|
connect_options() ->
|
||||||
|
[{packet, 4}, {mode, binary}, {active, true}].
|
||||||
|
|
||||||
|
|
||||||
|
-spec get_prime(realm()) -> host().
|
||||||
|
%% @private
|
||||||
|
%% Check the given Realm's config file for the current prime node and return it.
|
||||||
|
|
||||||
|
get_prime(Realm) ->
|
||||||
|
RealmFile = filename:join(zomp_dir(), Realm ++ ".realm"),
|
||||||
|
case file:consult(RealmFile) of
|
||||||
|
{ok, RealmMeta} ->
|
||||||
|
{prime, Prime} = lists:keyfind(prime, 1, RealmMeta),
|
||||||
|
Prime;
|
||||||
|
{error, enoent} ->
|
||||||
|
ok = log(error, "Missing realm file for ~tp (~tp).", [Realm, RealmFile]),
|
||||||
|
halt(1)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec hosts_cache_file(realm()) -> file:filename().
|
||||||
|
%% @private
|
||||||
|
%% Given a Realm name, construct a realm's .hosts filename and return it.
|
||||||
|
|
||||||
|
hosts_cache_file(Realm) ->
|
||||||
|
filename:join(zomp_dir(), Realm ++ ".hosts").
|
||||||
|
|
||||||
|
|
||||||
-spec disconnect(gen_tcp:socket()) -> ok.
|
-spec disconnect(gen_tcp:socket()) -> ok.
|
||||||
%% @private
|
%% @private
|
||||||
%% Gracefully shut down a socket, logging (but sidestepping) the case when the socket
|
%% Gracefully shut down a socket, logging (but sidestepping) the case when the socket
|
||||||
@ -1219,22 +1389,6 @@ extract_zrp(FileName) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec read_meta() -> [term()] | no_return().
|
|
||||||
%% @private
|
|
||||||
%% Read the `zomp.meta' file from the current directory, if possible. If not possible
|
|
||||||
%% then halt execution with an appropriate error message.
|
|
||||||
|
|
||||||
read_meta() ->
|
|
||||||
case file:consult("zomp.meta") of
|
|
||||||
{ok, Meta} ->
|
|
||||||
Meta;
|
|
||||||
Error ->
|
|
||||||
ok = log(error, "Failed to open \"zomp.meta\" with ~tp", [Error]),
|
|
||||||
ok = log(error, "Wrong directory?"),
|
|
||||||
halt(1)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec verify(Data, Signature, PubKey) -> ok | no_return()
|
-spec verify(Data, Signature, PubKey) -> ok | no_return()
|
||||||
when Data :: binary(),
|
when Data :: binary(),
|
||||||
Signature :: binary(),
|
Signature :: binary(),
|
||||||
@ -1300,6 +1454,52 @@ fetch(Socket, Needed) ->
|
|||||||
|
|
||||||
%%% Utility functions
|
%%% Utility functions
|
||||||
|
|
||||||
|
-spec read_meta() -> package_meta() | no_return().
|
||||||
|
%% @private
|
||||||
|
%% @equiv read_meta(".")
|
||||||
|
|
||||||
|
read_meta() ->
|
||||||
|
read_meta(".").
|
||||||
|
|
||||||
|
|
||||||
|
-spec read_meta(Dir) -> package_meta() | no_return()
|
||||||
|
when Dir :: file:filename().
|
||||||
|
%% @private
|
||||||
|
%% Read the `zomp.meta' file from the indicated directory, if possible. If not possible
|
||||||
|
%% then halt execution with an appropriate error message.
|
||||||
|
|
||||||
|
read_meta(Dir) ->
|
||||||
|
Path = filename:join(Dir, "zomp.meta"),
|
||||||
|
case file:consult(Path) of
|
||||||
|
{ok, Meta} ->
|
||||||
|
maps:from_list(Meta);
|
||||||
|
Error ->
|
||||||
|
ok = log(error, "Failed to open \"zomp.meta\" with ~tp", [Error]),
|
||||||
|
ok = log(error, "Wrong directory?"),
|
||||||
|
halt(1)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec write_meta(package_meta()) -> ok.
|
||||||
|
%% @private
|
||||||
|
%% @equiv write_meta(".")
|
||||||
|
|
||||||
|
write_meta() ->
|
||||||
|
write_meta(".").
|
||||||
|
|
||||||
|
|
||||||
|
-spec write_meta(Dir, Meta) -> ok
|
||||||
|
when Dir :: file:filename(),
|
||||||
|
Meta :: package_meta().
|
||||||
|
%% @private
|
||||||
|
%% Write the contents of the provided meta structure (a map these days) as a list of
|
||||||
|
%% Erlang K/V terms.
|
||||||
|
|
||||||
|
write_meta(Dir, Meta) ->
|
||||||
|
Path = filename:join(Dir, "zomp.meta"),
|
||||||
|
ok = write_terms(Path, maps:to_list(Meta)).
|
||||||
|
|
||||||
|
|
||||||
-spec write_terms(Filename, Terms) -> ok
|
-spec write_terms(Filename, Terms) -> ok
|
||||||
when Filename :: file:filename(),
|
when Filename :: file:filename(),
|
||||||
Terms :: [term()].
|
Terms :: [term()].
|
||||||
@ -1351,6 +1551,8 @@ build() ->
|
|||||||
%% Take a list of dependencies and return a list of dependencies that are not yet
|
%% Take a list of dependencies and return a list of dependencies that are not yet
|
||||||
%% installed on the system.
|
%% installed on the system.
|
||||||
|
|
||||||
|
scrub([]) ->
|
||||||
|
[];
|
||||||
scrub(Deps) ->
|
scrub(Deps) ->
|
||||||
{ok, Names} = file:list_dir("lib"),
|
{ok, Names} = file:list_dir("lib"),
|
||||||
Existing = lists:map(fun package_id/1, Names),
|
Existing = lists:map(fun package_id/1, Names),
|
||||||
@ -1382,7 +1584,8 @@ valid_lower0_9([$_ | _], $_) ->
|
|||||||
false;
|
false;
|
||||||
valid_lower0_9([Char | Rest], _)
|
valid_lower0_9([Char | Rest], _)
|
||||||
when $a =< Char, Char =< $z;
|
when $a =< Char, Char =< $z;
|
||||||
$0 =< Char, Char =< $9 ->
|
$0 =< Char, Char =< $9;
|
||||||
|
Char == $_ ->
|
||||||
valid_lower0_9(Rest, Char);
|
valid_lower0_9(Rest, Char);
|
||||||
valid_lower0_9([], _) ->
|
valid_lower0_9([], _) ->
|
||||||
true;
|
true;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user