package, run, install, etc.
This commit is contained in:
parent
f20f205c4f
commit
f630d4d88e
@ -1,5 +0,0 @@
|
||||
{realm,"otpr"}.
|
||||
{username,"zxq9"}.
|
||||
{realmname,"Craig Everett"}.
|
||||
{contact_info,{"email","zxq9@zxq9.com"}}.
|
||||
{keys,["zxq9-root"]}.
|
||||
@ -9,4 +9,4 @@
|
||||
zx_conn,
|
||||
zx_lib,
|
||||
zx_net]},
|
||||
{mod, {zx, []}}]}.
|
||||
{mod, {zx, none}}]}.
|
||||
|
||||
@ -100,10 +100,9 @@ do(["list", "deps"]) ->
|
||||
done(zx_local:list_deps());
|
||||
do(["list", "deps", PackageString]) ->
|
||||
done(zx_local:list_deps(PackageString));
|
||||
do(["import", "zrp", PackageFile]) ->
|
||||
done(zx_daemon:import_zrp(PackageFile));
|
||||
do(["install", PackageString]) ->
|
||||
done(zx_daemon:install(PackageString));
|
||||
do(["install", PackageFile]) ->
|
||||
ok = start(),
|
||||
done(zx_daemon:install(PackageFile));
|
||||
do(["set", "dep", PackageString]) ->
|
||||
done(zx_local:set_dep(PackageString));
|
||||
do(["set", "version", VersionString]) ->
|
||||
@ -231,7 +230,8 @@ compatibility_check(Platforms) ->
|
||||
%% @equiv application:ensure_started(zx).
|
||||
|
||||
start() ->
|
||||
application:ensure_started(zx).
|
||||
ok = application:ensure_started(zx),
|
||||
zx_daemon:init_connections().
|
||||
|
||||
|
||||
-spec stop() -> ok | {error, Reason :: term()}.
|
||||
@ -251,14 +251,14 @@ stop() ->
|
||||
|
||||
-spec start(StartType, StartArgs) -> Result
|
||||
when StartType :: normal,
|
||||
StartArgs :: [],
|
||||
StartArgs :: none,
|
||||
Result :: {ok, pid()}.
|
||||
%% @private
|
||||
%% Application callback. Not to be called directly.
|
||||
|
||||
start(normal, []) ->
|
||||
start(normal, none) ->
|
||||
ok = application:ensure_started(inets),
|
||||
zx_daemon:start_link().
|
||||
zx_sup:start_link().
|
||||
|
||||
|
||||
-spec stop(term()) -> ok.
|
||||
@ -373,36 +373,34 @@ prepare(PackageID, Meta, Dir, RunArgs) ->
|
||||
Deps = maps:get(deps, Meta),
|
||||
NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end,
|
||||
Needed = lists:filter(NotInstalled, Deps),
|
||||
Pending = lists:map(fun zx_daemon:fetch/1, Needed),
|
||||
case await_fetches(Pending) of
|
||||
ok ->
|
||||
ok = lists:foreach(fun install/1, Needed),
|
||||
ok = lists:foreach(fun build/1, Needed),
|
||||
execute(Type, PackageID, Meta, Dir, RunArgs);
|
||||
{error, Errors} ->
|
||||
error_exit("Failed package fetches: ~tp", [Errors], ?LINE)
|
||||
ok = lists:foreach(fun install/1, Needed),
|
||||
ok = lists:foreach(fun build/1, Needed),
|
||||
execute(Type, PackageID, Meta, Dir, RunArgs).
|
||||
|
||||
|
||||
-spec install(PackageString :: string()) -> zx:outcome().
|
||||
%% @private
|
||||
%% Installs a package from upstream.
|
||||
|
||||
install(PackageString) ->
|
||||
{ok, ID} = zx_daemon:install(PackageString),
|
||||
install(PackageString, ID).
|
||||
|
||||
|
||||
install(PackageString, ID) ->
|
||||
receive
|
||||
{z_result, ID, done} ->
|
||||
ok;
|
||||
{z_result, ID, {hops, Count}} ->
|
||||
ok = log(info, "~ts ~w hops away.", [PackageString, Count]),
|
||||
install(PackageString, ID);
|
||||
{z_result, ID, {error, Reason}} ->
|
||||
{error, Reason, 1}
|
||||
after 60000 ->
|
||||
{error, timeout, 62}
|
||||
end.
|
||||
|
||||
|
||||
await_fetches([]) -> ok;
|
||||
await_fetches(Pending) -> await_fetches(Pending, []).
|
||||
|
||||
|
||||
await_fetches([], []) ->
|
||||
ok;
|
||||
await_fetches([], Errors) ->
|
||||
{error, Errors};
|
||||
await_fetches(Pending, Errors) ->
|
||||
{NewPending, NewErrors} =
|
||||
receive
|
||||
{z_reply, ID, ok} ->
|
||||
{lists:delete(ID, Pending), Errors};
|
||||
{z_reply, ID, {error, Package, Reason}} ->
|
||||
{lists:delete(ID, Pending), [{Package, Reason} | Errors]}
|
||||
end,
|
||||
await_fetches(NewPending, NewErrors).
|
||||
|
||||
|
||||
-spec execute(Type, PackageID, Meta, Dir, RunArgs) -> no_return()
|
||||
when Type :: app | lib,
|
||||
PackageID :: package_id(),
|
||||
@ -518,7 +516,7 @@ resolve_installed_version({Realm, Name, Version}) ->
|
||||
|
||||
resolve_installed_version(PackageDir, Version) ->
|
||||
DirStrings = filelib:wildcard("*", PackageDir),
|
||||
Versions = lists:fold(fun tuplize/2, [], DirStrings),
|
||||
Versions = lists:foldl(fun tuplize/2, [], DirStrings),
|
||||
zx_lib:find_latest_compatible(Version, Versions).
|
||||
|
||||
|
||||
@ -540,14 +538,15 @@ build(PackageID) ->
|
||||
file:set_cwd(CWD).
|
||||
|
||||
|
||||
-spec ensure_package_dirs(package_id()) -> ok.
|
||||
%% @private
|
||||
%% Procedure to guarantee that directory locations necessary for the indicated app to
|
||||
%% run have been created or halt execution.
|
||||
|
||||
ensure_package_dirs(PackageID) ->
|
||||
Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
||||
lists:foreach(fun zx_lib:force_dir/1, Dirs).
|
||||
%% FIXME
|
||||
%-spec ensure_package_dirs(package_id()) -> ok.
|
||||
%%% @private
|
||||
%%% Procedure to guarantee that directory locations necessary for the indicated app to
|
||||
%%% run have been created or halt execution.
|
||||
%
|
||||
%ensure_package_dirs(PackageID) ->
|
||||
% Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
||||
% lists:foreach(fun zx_lib:force_dir/1, Dirs).
|
||||
|
||||
|
||||
|
||||
|
||||
@ -98,14 +98,15 @@ list_resigns(Realm) ->
|
||||
submit(ZspPath) ->
|
||||
{ok, ZspBin} = file:read_file(ZspPath),
|
||||
<<SigSize:24, Sig:SigSize/binary, Signed/binary>> = ZspBin,
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, TarGz/binary>> = Signed,
|
||||
{ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin),
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, _/binary>> = Signed,
|
||||
{ok, {PackageID, SigKeyName, _}} = zx_lib:b_to_ts(MetaBin),
|
||||
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||
true = zx_key:verify(Signed, Sig, PubKey),
|
||||
{ok, Socket} = connect_auth(element(1, PackageID)),
|
||||
ok = send(Socket, {submit, PackageID}),
|
||||
ok = recv_or_die(Socket),
|
||||
ok = gen_tcp:send(Socket, ZspBin),
|
||||
ok = log(info, "Done sending contents of ~tp", [PackageFile]),
|
||||
ok = log(info, "Done sending contents of ~tp", [ZspPath]),
|
||||
Outcome = recv_or_die(Socket),
|
||||
log(info, "Response: ~tp", [Outcome]),
|
||||
disconnect(Socket).
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([start_conn/2]).
|
||||
-export([start_conn/1]).
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
@ -18,9 +18,8 @@
|
||||
|
||||
%%% Interface Functions
|
||||
|
||||
-spec start_conn(Host, Serial) -> Result
|
||||
-spec start_conn(Host) -> Result
|
||||
when Host :: zx:host(),
|
||||
Serial :: zx:serial(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: term().
|
||||
@ -28,8 +27,8 @@
|
||||
%% Start an upstream connection handler.
|
||||
%% (Should only be called from zx_conn).
|
||||
|
||||
start_conn(Host, Serial) ->
|
||||
supervisor:start_child(?MODULE, [Host, Serial]).
|
||||
start_conn(Host) ->
|
||||
supervisor:start_child(?MODULE, [Host]).
|
||||
|
||||
|
||||
|
||||
|
||||
@ -147,11 +147,10 @@
|
||||
-export([pass_meta/3,
|
||||
subscribe/1, unsubscribe/1,
|
||||
list/0, list/1, list/2, list/3, latest/1,
|
||||
fetch/1, verify_key/1,
|
||||
install/1, import_zsp/1,
|
||||
verify_key/1, fetch/1, install/1,
|
||||
pending/1, packagers/1, maintainers/1, sysops/1]).
|
||||
-export([report/1, result/2, notify/2]).
|
||||
-export([start_link/0, stop/0]).
|
||||
-export([start_link/0, init_connections/0, stop/0]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
code_change/3, terminate/2]).
|
||||
|
||||
@ -189,14 +188,12 @@
|
||||
|
||||
|
||||
-record(rmeta,
|
||||
{revision = 0 :: non_neg_integer(),
|
||||
serial = 0 :: non_neg_integer(),
|
||||
{serial = 0 :: non_neg_integer(),
|
||||
prime = {"zomp.tsuriai.jp", 11311} :: zx:host(),
|
||||
private = [] :: [zx:host()],
|
||||
mirrors = queue:new() :: queue:queue(zx:host()),
|
||||
realm_keys = [] :: [zx:key_meta()],
|
||||
package_keys = [] :: [zx:key_meta()],
|
||||
sysops = [] :: [zx:sysop_meta()],
|
||||
key = [] :: zx:key_name(),
|
||||
sysop = none :: zx:user_name(),
|
||||
assigned = none :: none | pid(),
|
||||
available = [] :: [pid()]}).
|
||||
|
||||
@ -450,25 +447,6 @@ latest({Realm, Name, Version}) ->
|
||||
request({latest, Realm, Name, Version}).
|
||||
|
||||
|
||||
-spec fetch(PackageID) -> {ok, RequestID}
|
||||
when PackageID :: zx:package_id(),
|
||||
RequestID :: integer().
|
||||
%% @doc
|
||||
%% Ensure a package is available locally, or queue it for download otherwise.
|
||||
%% Returns a request ID which will be returned in a message with the result from an
|
||||
%% upstream zomp node. Crashes the caller on an illegal realm name, package name or
|
||||
%% version tuple.
|
||||
%%
|
||||
%% Response messages are of the type `result()' where the third element is of the
|
||||
%% type `fetch_result()'.
|
||||
|
||||
fetch({Realm, Name, Version}) ->
|
||||
true = zx_lib:valid_lower0_9(Realm),
|
||||
true = zx_lib:valid_lower0_9(Name),
|
||||
true = zx_lib:valid_version(Version),
|
||||
request({fetch, Realm, Name, Version}).
|
||||
|
||||
|
||||
-spec verify_key(KeyID) -> {ok, RequestID}
|
||||
when KeyID :: zx:key_id(),
|
||||
RequestID :: id().
|
||||
@ -486,24 +464,25 @@ verify_key({Realm, KeyName}) ->
|
||||
request({verify_key, Realm, KeyName}).
|
||||
|
||||
|
||||
-spec import_zsp(Path :: file:filename()) -> zx:outcome().
|
||||
-spec install(Path :: file:filename()) -> zx:outcome().
|
||||
%% @doc
|
||||
%% Install a package from a local file.
|
||||
|
||||
import_zsp(Path) ->
|
||||
gen_server:call(?MODULE, {import_zsp, Path}).
|
||||
install(Path) ->
|
||||
gen_server:call(?MODULE, {install, Path}).
|
||||
|
||||
|
||||
-spec install(PackageString :: string()) -> zx:outcome().
|
||||
-spec fetch(PackageString :: string()) -> {ok, id()}.
|
||||
%% @doc
|
||||
%% Install the specified package.
|
||||
%% Install the specified package. This returns an id() that will be referenced
|
||||
%% in a later response message.
|
||||
|
||||
install(PackageString) ->
|
||||
fetch(PackageString) ->
|
||||
case zx_lib:package_id(PackageString) of
|
||||
{ok, PackageID} ->
|
||||
gen_server:call(?MODULE, {install, PackageID});
|
||||
gen_server:call(?MODULE, {fetch, PackageID});
|
||||
{error, invalid_package_string} ->
|
||||
{error, "Invalid package string. Try again.", 22}
|
||||
{error, "Invalid package string.", 22}
|
||||
end.
|
||||
|
||||
|
||||
@ -632,63 +611,13 @@ start_link() ->
|
||||
%% TODO: Implement lockfile checking and master lock acquisition.
|
||||
|
||||
init(none) ->
|
||||
Blank = blank_state(),
|
||||
{ok, MX, CX} = init_connections(),
|
||||
State = Blank#s{mx = MX, cx = CX},
|
||||
{ok, State}.
|
||||
{ok, #s{}}.
|
||||
|
||||
|
||||
-spec blank_state() -> state().
|
||||
%% @private
|
||||
%% Used to generate a correct, but exactly empty state.
|
||||
%% Useful mostly for testing and validation, though also actually used in the program.
|
||||
|
||||
blank_state() ->
|
||||
#s{}.
|
||||
|
||||
|
||||
-spec init_connections() -> {ok, MX, CX}
|
||||
when MX :: monitor_index(),
|
||||
CX :: conn_index().
|
||||
%% @private
|
||||
%% Starting from a stateless condition, recruit and resolve all realm relevant data,
|
||||
%% populate host caches, and initiate connections to required realms. On completion
|
||||
%% return a populated MX and CX to the caller. Should only ever be called by init/1.
|
||||
%% Returns an `ok' tuple to disambiguate it from pure functions *and* to leave an
|
||||
%% obvious place to populate error returns in the future if desired.
|
||||
-spec init_connections() -> ok.
|
||||
|
||||
init_connections() ->
|
||||
CX = cx_load(),
|
||||
MX = mx_new(),
|
||||
Realms = cx_realms(CX),
|
||||
init_connections(Realms, MX, CX).
|
||||
|
||||
|
||||
-spec init_connections(Realms, MX, CX) -> {ok, NewMX, NewCX}
|
||||
when Realms :: [zx:realm()],
|
||||
MX :: monitor_index(),
|
||||
CX :: conn_index(),
|
||||
NewMX :: monitor_index(),
|
||||
NewCX :: conn_index().
|
||||
|
||||
init_connections([Realm | Realms], MX, CX) ->
|
||||
{ok, Hosts, NextCX} = cx_next_hosts(3, Realm, CX),
|
||||
MaybeAttempt =
|
||||
fun(Host, {M, C}) ->
|
||||
case cx_maybe_add_attempt(Host, Realm, C) of
|
||||
not_connected ->
|
||||
{ok, Pid} = zx_conn:start(Host),
|
||||
NewM = mx_add_monitor(Pid, attempt, M),
|
||||
NewC = cx_add_attempt(Pid, Host, Realm, C),
|
||||
{NewM, NewC};
|
||||
{ok, NewC} ->
|
||||
{M, NewC}
|
||||
end
|
||||
end,
|
||||
{NewMX, NewCX} = lists:foldl(MaybeAttempt, {MX, NextCX}, Hosts),
|
||||
init_connections(Realms, NewMX, NewCX);
|
||||
init_connections([], MX, CX) ->
|
||||
{ok, MX, CX}.
|
||||
gen_server:cast(?MODULE, init_connections).
|
||||
|
||||
|
||||
|
||||
@ -718,11 +647,16 @@ handle_call({request, Action}, From, State = #s{id = ID}) ->
|
||||
NextState = do_request(Requestor, Action, State#s{id = NewID}),
|
||||
NewState = eval_queue(NextState),
|
||||
{noreply, NewState};
|
||||
handle_call({install, PackageID}, _, State) ->
|
||||
{Result, NewState} = do_install(PackageID, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({import_zsp, Path}, _, State) ->
|
||||
handle_call({fetch, PackageID}, From, State = #s{id = ID}) ->
|
||||
NewID = ID + 1,
|
||||
_ = gen_server:reply(From, {ok, NewID}),
|
||||
Requestor = element(1, From),
|
||||
NextState = do_fetch(PackageID, Requestor, State#s{id = NewID}),
|
||||
NewState = eval_queue(NextState),
|
||||
{noreply, NewState};
|
||||
handle_call({install, Path}, _, State) ->
|
||||
Result = do_import_zsp(Path),
|
||||
NewState = eval_queue(State),
|
||||
{reply, Result, NewState};
|
||||
handle_call(Unexpected, From, State) ->
|
||||
ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]),
|
||||
@ -755,6 +689,9 @@ handle_cast({notify, Conn, Package, Update}, State) ->
|
||||
ok = do_notify(Conn, Package, Update, State),
|
||||
NewState = eval_queue(State),
|
||||
{noreply, NewState};
|
||||
handle_cast(init_connections, State) ->
|
||||
NewState = init_connections(State),
|
||||
{noreply, NewState};
|
||||
handle_cast(stop, State) ->
|
||||
{stop, normal, State};
|
||||
handle_cast(Unexpected, State) ->
|
||||
@ -942,6 +879,49 @@ dequeue(Pending) ->
|
||||
end.
|
||||
|
||||
|
||||
-spec init_connections(State) -> NewState
|
||||
when State :: state(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% Starting from a stateless condition, recruit and resolve all realm relevant data,
|
||||
%% populate host caches, and initiate connections to required realms. On completion
|
||||
%% return a populated MX and CX to the caller. Should only ever be called by init/1.
|
||||
%% Returns an `ok' tuple to disambiguate it from pure functions *and* to leave an
|
||||
%% obvious place to populate error returns in the future if desired.
|
||||
|
||||
init_connections(State = #s{mx = MX, cx = CX}) ->
|
||||
Realms = cx_realms(CX),
|
||||
{ok, NewMX, NewCX} = init_connections(Realms, MX, CX),
|
||||
State#s{mx = NewMX, cx = NewCX}.
|
||||
|
||||
|
||||
-spec init_connections(Realms, MX, CX) -> {ok, NewMX, NewCX}
|
||||
when Realms :: [zx:realm()],
|
||||
MX :: monitor_index(),
|
||||
CX :: conn_index(),
|
||||
NewMX :: monitor_index(),
|
||||
NewCX :: conn_index().
|
||||
|
||||
init_connections([Realm | Realms], MX, CX) ->
|
||||
{ok, Hosts, NextCX} = cx_next_hosts(3, Realm, CX),
|
||||
MaybeAttempt =
|
||||
fun(Host, {M, C}) ->
|
||||
case cx_maybe_add_attempt(Host, Realm, C) of
|
||||
not_connected ->
|
||||
{ok, Pid} = zx_conn:start(Host),
|
||||
NewM = mx_add_monitor(Pid, attempt, M),
|
||||
NewC = cx_add_attempt(Pid, Host, Realm, C),
|
||||
{NewM, NewC};
|
||||
{ok, NewC} ->
|
||||
{M, NewC}
|
||||
end
|
||||
end,
|
||||
{NewMX, NewCX} = lists:foldl(MaybeAttempt, {MX, NextCX}, Hosts),
|
||||
init_connections(Realms, NewMX, NewCX);
|
||||
init_connections([], MX, CX) ->
|
||||
{ok, MX, CX}.
|
||||
|
||||
|
||||
-spec ensure_connections(Realms, MX, CX) -> {NewMX, NewCX}
|
||||
when Realms :: [zx:realm()],
|
||||
MX :: monitor_index(),
|
||||
@ -997,11 +977,14 @@ reassign_conns([], CX, Unassigned) ->
|
||||
do_result(ID, Result, State = #s{requests = Requests, dropped = Dropped, mx = MX}) ->
|
||||
{NewDropped, NewRequests, NewMX} =
|
||||
case maps:take(ID, Requests) of
|
||||
{Request, NextRequests} ->
|
||||
{Request, Rest} when element(1, element(2, Request)) == fetch ->
|
||||
{NextMX, NextR} = handle_fetch_result(ID, Result, Request, Rest, MX),
|
||||
{Dropped, NextR, NextMX};
|
||||
{Request, Rest} ->
|
||||
Requestor = element(1, Request),
|
||||
Requestor ! {z_result, ID, Result},
|
||||
NextMX = mx_del_monitor(Requestor, {requestor, ID}, MX),
|
||||
{Dropped, NextRequests, NextMX};
|
||||
{Dropped, Rest, NextMX};
|
||||
error ->
|
||||
NextDropped = handle_orphan_result(ID, Result, Dropped),
|
||||
{NextDropped, Requests, MX}
|
||||
@ -1009,6 +992,24 @@ do_result(ID, Result, State = #s{requests = Requests, dropped = Dropped, mx = MX
|
||||
State#s{requests = NewRequests, dropped = NewDropped, mx = NewMX}.
|
||||
|
||||
|
||||
handle_fetch_result(ID, {done, Bin}, {Requestor, _}, Requests, MX) ->
|
||||
Result =
|
||||
case do_import_package(Bin) of
|
||||
ok -> done;
|
||||
Error -> Error
|
||||
end,
|
||||
Requestor ! {z_result, ID, Result},
|
||||
NextMX = mx_del_monitor(Requestor, {requestor, ID}, MX),
|
||||
{NextMX, Requests};
|
||||
handle_fetch_result(ID, Hops = {hops, _}, Request = {Requestor, _}, Requests, MX) ->
|
||||
Requestor ! {z_result, ID, Hops},
|
||||
{MX, maps:put(ID, Request, Requests)};
|
||||
handle_fetch_result(ID, Outcome, {Requestor, _}, Requests, MX) ->
|
||||
Requestor ! {z_result, ID, Outcome},
|
||||
NextMX = mx_del_monitor(Requestor, {requestor, ID}, MX),
|
||||
{NextMX, Requests}.
|
||||
|
||||
|
||||
-spec handle_orphan_result(ID, Result, Dropped) -> NewDropped
|
||||
when ID :: id(),
|
||||
Result :: result(),
|
||||
@ -1237,22 +1238,32 @@ drop_requests(ReqIDs, Dropped, Requests) ->
|
||||
lists:fold(Partition, {Dropped, Requests}, ReqIDs).
|
||||
|
||||
|
||||
-spec install(PackageID, State) -> {Result, NewState}
|
||||
-spec do_fetch(PackageID, Requestor, State) -> NewState
|
||||
when PackageID :: zx:package_id(),
|
||||
Requestor :: pid(),
|
||||
State :: state(),
|
||||
Result :: zx:outcome(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% FIXME: This is about as useful as psuedocode at the moment. Meh.
|
||||
%% Provide a chance to bypass if the package is in cache.
|
||||
|
||||
install(PackageID, State) ->
|
||||
Path = zx_lib:zxp_path(PackageID),
|
||||
case file:is_regular(Path) of
|
||||
true ->
|
||||
do_install(PackageID, Path);
|
||||
false ->
|
||||
ok = do_fetch(PackageID),
|
||||
do_install(PackageID, Path)
|
||||
do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
|
||||
Path = zx_lib:zsp_path(PackageID),
|
||||
case file:read_file(Path) of
|
||||
{ok, Bin} ->
|
||||
case do_import_package(Bin) of
|
||||
ok ->
|
||||
Requestor ! {z_result, ID, ok},
|
||||
{ok, State};
|
||||
Error ->
|
||||
Requestor ! {z_result, ID, Error},
|
||||
{ok, State}
|
||||
end;
|
||||
{error, enoent} ->
|
||||
{Realm, Name, Version} = PackageID,
|
||||
Action = {fetch, Realm, Name, Version},
|
||||
do_request(Requestor, Action, State);
|
||||
Error ->
|
||||
Requestor ! {z_result, ID, Error}
|
||||
end.
|
||||
|
||||
|
||||
@ -1260,22 +1271,35 @@ install(PackageID, State) ->
|
||||
%% @private
|
||||
%% Dealing with data from the (probably local) filesystem can fail in a bajillion ways
|
||||
%% and spring memory leaks if one tries to get too clever. So I'm sidestepping all the
|
||||
%% madness with a "try++" here: spawning a suicidal helper.
|
||||
%% madness with a "try++" here by spawning a suicidal helper.
|
||||
|
||||
do_import_zsp(Path) ->
|
||||
{Pid, Mon} = spawn_monitor(fun() -> actually_import(Path) end),
|
||||
{Pid, Mon} = spawn_monitor(fun() -> import_from_path(Path) end),
|
||||
receive
|
||||
{Pid, Outcome} ->
|
||||
true = demonitor(Mon, [flush]),
|
||||
Outcome;
|
||||
{'DOWN', Pid, process, Mon, Info} ->
|
||||
{error, Info};
|
||||
{error, Info}
|
||||
after 5000 ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
-spec actually_import(ZspPath) -> no_return()
|
||||
do_import_package(Bin) ->
|
||||
{Pid, Mon} = spawn_monitor(fun() -> import_package(Bin) end),
|
||||
receive
|
||||
{Pid, Outcome} ->
|
||||
true = demonitor(Mon, [flush]),
|
||||
Outcome;
|
||||
{'DOWN', Pid, process, Mon, Info} ->
|
||||
{error, Info}
|
||||
after 5000 ->
|
||||
{error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
-spec import_from_path(ZspPath) -> no_return()
|
||||
when ZspPath :: file:filename().
|
||||
%% @private
|
||||
%% The happy path of .zsp installation.
|
||||
@ -1302,10 +1326,13 @@ do_import_zsp(Path) ->
|
||||
%% If a place to get more fancy with the phases becomes really obvious after writing
|
||||
%% identicalish segements of functions a few places then I'll break things apart.
|
||||
|
||||
actually_import(ZspPath) ->
|
||||
{ok, Bin = <<Size:24, Sig:Size/binary, Signed/binary>>} = file:read_file(ZspPath),
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, TarGz/binary>> = Signed,
|
||||
{ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin),
|
||||
import_from_path(ZspPath) ->
|
||||
{ok, Bin} = file:read_file(ZspPath),
|
||||
import_package(Bin).
|
||||
|
||||
import_package(Bin = <<Size:24, Sig:Size/binary, Signed/binary>>) ->
|
||||
<<MetaSize:16, MetaBin:MetaSize/binary, TarGZ/binary>> = Signed,
|
||||
{PackageID, SigKeyName, _} = zx_lib:b_to_ts(MetaBin),
|
||||
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
|
||||
true = zx_key:verify(Signed, Sig, PubKey),
|
||||
ok = file:write_file(zx_lib:zsp_path(PackageID), Bin),
|
||||
@ -1313,8 +1340,8 @@ actually_import(ZspPath) ->
|
||||
ok = filelib:ensure_dir(Destination),
|
||||
ok = zx_lib:rm_rf(Destination),
|
||||
ok = file:make_dir(Destination),
|
||||
ok = erl_tar:extract(TarGZ, [{cwd, Destination}]),
|
||||
zx_daemon ! {self(), ok}.
|
||||
Result = erl_tar:extract({binary, TarGZ}, [{cwd, Destination}, compressed]),
|
||||
zx_daemon ! {self(), Result}.
|
||||
|
||||
|
||||
|
||||
@ -1475,36 +1502,30 @@ cx_load() ->
|
||||
%% where any number of wild things might be going on in the user's filesystem).
|
||||
|
||||
cx_populate() ->
|
||||
Pattern = filename:join([zx_lib:path(etc), "*", "realm.conf"]),
|
||||
case filelib:wildcard(Pattern) of
|
||||
[] -> {error, no_realms};
|
||||
RealmFiles -> {ok, cx_populate(RealmFiles, [])}
|
||||
end.
|
||||
Realms = zx_lib:list_realms(),
|
||||
CX = lists:foldl(fun cx_populate/2, [], Realms),
|
||||
{ok, CX}.
|
||||
|
||||
|
||||
-spec cx_populate(RealmFiles, Realms) -> NewRealms
|
||||
when RealmFiles :: file:filename(),
|
||||
Realms :: [{zx:realm(), realm_meta()}],
|
||||
NewRealms :: [{zx:realm(), realm_meta()}].
|
||||
-spec cx_populate(Realms, CX) -> NewCX
|
||||
when Realms :: [zx:realm()],
|
||||
CX :: [{zx:realm(), realm_meta()}],
|
||||
NewCX :: [{zx:realm(), realm_meta()}].
|
||||
%% @private
|
||||
%% Pack an initially empty conn_index() with realm meta and host cache data.
|
||||
%% Should not halt on a corrupted, missing, malformed, etc. realm file but will log
|
||||
%% any loading errors.
|
||||
|
||||
cx_populate([File | Files], Realms) ->
|
||||
NewRealms =
|
||||
case file:consult(File) of
|
||||
{ok, Meta} ->
|
||||
Realm = cx_load_realm_meta(Meta),
|
||||
[Realm | Realms];
|
||||
{error, Reason} ->
|
||||
Message = "Loading realm file ~tp failed with: ~tp. Skipping...",
|
||||
ok = log(warning, Message, [File, Reason]),
|
||||
Realms
|
||||
end,
|
||||
cx_populate(Files, NewRealms);
|
||||
cx_populate([], Realms) ->
|
||||
Realms.
|
||||
cx_populate(Realm, CX) ->
|
||||
case zx_lib:load_realm_conf(Realm) of
|
||||
{ok, Meta} ->
|
||||
Record = cx_load_realm_meta(Meta),
|
||||
[{Realm, Record} | CX];
|
||||
{error, Reason} ->
|
||||
Message = "Loading realm ~tp failed with: ~tp. Skipping...",
|
||||
ok = log(warning, Message, [Realm, Reason]),
|
||||
CX
|
||||
end.
|
||||
|
||||
|
||||
-spec cx_load_realm_meta(Meta) -> Result
|
||||
@ -1514,20 +1535,12 @@ cx_populate([], Realms) ->
|
||||
%% This function MUST adhere to the realmfile definition found at.
|
||||
|
||||
cx_load_realm_meta(Meta) ->
|
||||
{realm, Realm} = lists:keyfind(realm, 1, Meta),
|
||||
{revision, Revision} = lists:keyfind(revision, 1, Meta),
|
||||
{prime, Prime} = lists:keyfind(prime, 1, Meta),
|
||||
{realm_keys, RealmKeys} = lists:keyfind(realm_keys, 1, Meta),
|
||||
{package_keys, PackageKeys} = lists:keyfind(packge_keys, 1, Meta),
|
||||
{sysops, Sysops} = lists:keyfind(sysops, 1, Meta),
|
||||
Realm = maps:get(realm, Meta),
|
||||
Basic =
|
||||
#rmeta{revision = Revision,
|
||||
prime = Prime,
|
||||
realm_keys = RealmKeys,
|
||||
package_keys = PackageKeys,
|
||||
sysops = Sysops},
|
||||
Complete = cx_load_cache(Realm, Basic),
|
||||
{Realm, Complete}.
|
||||
#rmeta{prime = maps:get(prime, Meta),
|
||||
sysop = maps:get(sysop, Meta),
|
||||
key = maps:get(key, Meta)},
|
||||
cx_load_cache(Realm, Basic).
|
||||
|
||||
|
||||
-spec cx_load_cache(Realm, Basic) -> Complete
|
||||
@ -1651,7 +1664,7 @@ cx_next_host(Meta = #rmeta{prime = Prime, private = Private, mirrors = Mirrors})
|
||||
{ok, Next, Meta#rmeta{mirrors = NewMirrors}};
|
||||
{empty, Mirrors} ->
|
||||
Enqueue = fun(H, Q) -> queue:in(H, Q) end,
|
||||
NewMirrors = lists:foldl(Enqueue, Private, Mirrors),
|
||||
NewMirrors = lists:foldl(Enqueue, Mirrors, Private),
|
||||
{prime, Prime, Meta#rmeta{mirrors = NewMirrors}}
|
||||
end.
|
||||
|
||||
|
||||
@ -31,15 +31,15 @@ ensure_keypair(KeyID = {Realm, KeyName}) ->
|
||||
true;
|
||||
{false, true} ->
|
||||
Format = "Public key ~tp/~tp cannot be found",
|
||||
Message = io_lib:format(Message, [Realm, KeyName]),
|
||||
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||
{error, Message, 2};
|
||||
{true, false} ->
|
||||
Format = "Private key ~tp/~tp cannot be found",
|
||||
Message = io_lib:format(Message, [Realm, KeyName]),
|
||||
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||
{error, Message, 2};
|
||||
{false, false} ->
|
||||
Format = "Key pair ~tp/~tp cannot be found",
|
||||
Message = io_lib:format(Message, [Realm, KeyName]),
|
||||
Message = io_lib:format(Format, [Realm, KeyName]),
|
||||
{error, Message, 2}
|
||||
end.
|
||||
|
||||
@ -76,49 +76,47 @@ path(private, {Realm, KeyName}) ->
|
||||
|
||||
prompt_keygen() ->
|
||||
Message =
|
||||
"~n Enter a name for your new keys.~n~n"
|
||||
" Valid names must start with a lower-case letter, and can include~n"
|
||||
" only lower-case letters, numbers, and periods, but no series of~n"
|
||||
" consecutive periods. (That is: [a-z0-9\\.])~n~n"
|
||||
" To designate the key as realm-specific, enter the realm name and~n"
|
||||
" key name separated by a space.~n~n"
|
||||
" Example: some.realm my.key~n",
|
||||
"~nKEY NAME~n"
|
||||
"Enter a name for your new key pair.~n"
|
||||
"Valid names must start with a lower-case letter, and can include "
|
||||
"only lower-case letters, numbers, and underscores, but no series of "
|
||||
"consecutive underscores. (That is: [a-z0-9_])~n"
|
||||
" Example: my_key~n",
|
||||
ok = io:format(Message),
|
||||
Input = zx_tty:get_input(),
|
||||
{Realm, KeyName} =
|
||||
case string:lexemes(Input, " ") of
|
||||
[R, K] -> {R, K};
|
||||
[K] -> {"otpr", K}
|
||||
end,
|
||||
case {zx_lib:valid_lower0_9(Realm), zx_lib:valid_label(KeyName)} of
|
||||
{true, true} ->
|
||||
{Realm, KeyName};
|
||||
{false, true} ->
|
||||
ok = io:format("Bad realm name ~tp. Try again.~n", [Realm]),
|
||||
prompt_keygen();
|
||||
{true, false} ->
|
||||
ok = io:format("Bad key name ~tp. Try again.~n", [KeyName]),
|
||||
prompt_keygen();
|
||||
{false, false} ->
|
||||
ok = io:format("NUTS! Both key and realm names are illegal. Try again.~n"),
|
||||
case zx_lib:valid_lower0_9(Input) of
|
||||
true ->
|
||||
Input;
|
||||
false ->
|
||||
ok = io:format("Bad key name ~tp. Try again.~n", [Input]),
|
||||
prompt_keygen()
|
||||
end.
|
||||
|
||||
|
||||
-spec generate_rsa(KeyID) -> Result
|
||||
when KeyID :: zx:key_id(),
|
||||
Result :: ok
|
||||
| {error, keygen_fail}.
|
||||
when KeyID :: zx:key_id(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: keygen_fail
|
||||
| exists.
|
||||
%% @private
|
||||
%% Generate an RSA keypair and write them in der format to the current directory, using
|
||||
%% filenames derived from Prefix.
|
||||
%% NOTE: The current version of this command is likely to only work on a unix system.
|
||||
|
||||
generate_rsa(KeyID = {Realm, KeyName}) ->
|
||||
PemFile = filename:join(zx_lib:path(key, Realm), KeyName ++ ".pub.pem"),
|
||||
BaseName = filename:join(zx_lib:path(key, Realm), KeyName),
|
||||
Pattern = BaseName ++ ".*.der",
|
||||
case filelib:wildcard(Pattern) of
|
||||
[] -> generate_rsa(KeyID, BaseName);
|
||||
_ -> {error, exists}
|
||||
end.
|
||||
|
||||
|
||||
generate_rsa(KeyID, BaseName) ->
|
||||
PemFile = BaseName ++ ".pub.pem",
|
||||
KeyFile = path(private, KeyID),
|
||||
PubFile = path(public, KeyID),
|
||||
ok = lists:foreach(fun zx_lib:halt_if_exists/1, [PemFile, KeyFile, PubFile]),
|
||||
ok = log(info, "Generating ~p and ~p. Please be patient...", [KeyFile, PubFile]),
|
||||
case gen_p_key(KeyFile) of
|
||||
ok ->
|
||||
@ -144,7 +142,7 @@ generate_rsa(KeyID = {Realm, KeyName}) ->
|
||||
|
||||
|
||||
-spec gen_p_key(KeyFile) -> Result
|
||||
when KeyFile :: file:filename()
|
||||
when KeyFile :: file:filename(),
|
||||
Result :: ok
|
||||
| {error, no_ssl}.
|
||||
%% @private
|
||||
@ -179,16 +177,21 @@ gen_p_key(KeyFile) ->
|
||||
%% way.
|
||||
|
||||
der_to_pem(KeyFile, PemFile) ->
|
||||
Command =
|
||||
io_lib:format("~ts rsa"
|
||||
" -inform DER"
|
||||
" -in ~ts"
|
||||
" -outform PEM"
|
||||
" -pubout"
|
||||
" -out ~ts",
|
||||
[openssl(), KeyFile, PemFile]),
|
||||
Out = os:cmd(Command),
|
||||
io:format(Out).
|
||||
case openssl() of
|
||||
{ok, OpenSSL} ->
|
||||
Command =
|
||||
io_lib:format("~ts rsa"
|
||||
" -inform DER"
|
||||
" -in ~ts"
|
||||
" -outform PEM"
|
||||
" -pubout"
|
||||
" -out ~ts",
|
||||
[OpenSSL, KeyFile, PemFile]),
|
||||
Out = os:cmd(Command),
|
||||
io:format(Out);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
-spec check_key(KeyFile, PubFile) -> Result
|
||||
@ -230,7 +233,7 @@ openssl() ->
|
||||
{error, no_ssl};
|
||||
Path ->
|
||||
log(info, "OpenSSL executable found at: ~ts", [Path]),
|
||||
OpenSSL
|
||||
{ok, OpenSSL}
|
||||
end.
|
||||
|
||||
|
||||
@ -266,29 +269,3 @@ load(Type, KeyID) ->
|
||||
|
||||
verify(Data, Signature, PubKey) ->
|
||||
public_key:verify(Data, sha512, Signature, PubKey).
|
||||
|
||||
|
||||
|
||||
%%% Error exits
|
||||
|
||||
-spec error_exit(Error, Line) -> no_return()
|
||||
when Error :: term(),
|
||||
Line :: non_neg_integer().
|
||||
%% @private
|
||||
%% Format an error message in a way that makes it easy to locate.
|
||||
|
||||
error_exit(Error, Line) ->
|
||||
error_exit(Error, [], Line).
|
||||
|
||||
|
||||
-spec error_exit(Format, Args, Line) -> no_return()
|
||||
when Format :: string(),
|
||||
Args :: [term()],
|
||||
Line :: non_neg_integer().
|
||||
%% @private
|
||||
%% Format an error message in a way that makes it easy to locate.
|
||||
|
||||
error_exit(Format, Args, Line) ->
|
||||
File = filename:basename(?FILE),
|
||||
ok = log(error, "~ts:~tp: " ++ Format, [File, Line | Args]),
|
||||
halt(1).
|
||||
|
||||
@ -589,7 +589,7 @@ namify_zsp(PackageID) ->
|
||||
|
||||
-spec zsp_path(zx:package_id()) -> file:filename().
|
||||
|
||||
zsp_path({Realm, _, _}) ->
|
||||
zsp_path(PackageID = {Realm, _, _}) ->
|
||||
filename:join(path(zsp, Realm), namify_zsp(PackageID)).
|
||||
|
||||
|
||||
@ -750,18 +750,3 @@ b_to_ts(Binary) ->
|
||||
catch
|
||||
error:badarg -> error
|
||||
end.
|
||||
|
||||
|
||||
%%% Error exits
|
||||
|
||||
-spec error_exit(Format, Args, Line) -> no_return()
|
||||
when Format :: string(),
|
||||
Args :: [term()],
|
||||
Line :: non_neg_integer().
|
||||
%% @private
|
||||
%% Format an error message in a way that makes it easy to locate.
|
||||
|
||||
error_exit(Format, Args, Line) ->
|
||||
File = filename:basename(?FILE),
|
||||
ok = log(error, "~ts:~tp: " ++ Format, [File, Line | Args]),
|
||||
halt(1).
|
||||
|
||||
@ -73,14 +73,14 @@ initialize3(Type, PackageID) ->
|
||||
case package_exists(PackageID) of
|
||||
false ->
|
||||
Prefix = ask_prefix(),
|
||||
initialize(Type, PackageID, Prefix);
|
||||
initialize4(Type, PackageID, Prefix);
|
||||
true ->
|
||||
Message = "Package already exists. Try another.",
|
||||
{error, Message, 17}
|
||||
end.
|
||||
|
||||
|
||||
initialize(app, PackageID, Prefix) ->
|
||||
initialize4(app, PackageID, Prefix) ->
|
||||
Instructions =
|
||||
"The OTP application controller has to know what module to call start/2 on to "
|
||||
"start your program.~n"
|
||||
@ -93,23 +93,23 @@ initialize(app, PackageID, Prefix) ->
|
||||
ok = io:format(Instructions),
|
||||
case zx_tty:get_input() of
|
||||
"" ->
|
||||
initialize(lib, PackageID, Prefix, none);
|
||||
initialize5(lib, PackageID, Prefix, none);
|
||||
String ->
|
||||
case zx_lib:valid_lower0_9(String) of
|
||||
true ->
|
||||
AppStart = {list_to_atom(String), []},
|
||||
initialize(app, PackageID, Prefix, AppStart);
|
||||
initialize5(app, PackageID, Prefix, AppStart);
|
||||
false ->
|
||||
Message = "The name \"~ts\" doesn't seem valid. Try \"[a-z_]*\".~n",
|
||||
ok = io:format(Message, String),
|
||||
initialize(app, PackageID, Prefix)
|
||||
initialize4(app, PackageID, Prefix)
|
||||
end
|
||||
end;
|
||||
initialize(lib, PackageID, Prefix) ->
|
||||
initialize(lib, PackageID, Prefix, none).
|
||||
initialize4(lib, PackageID, Prefix) ->
|
||||
initialize5(lib, PackageID, Prefix, none).
|
||||
|
||||
|
||||
initialize(Type, PackageID, Prefix, AppStart) ->
|
||||
initialize5(Type, PackageID, Prefix, AppStart) ->
|
||||
ok = update_source_vsn(element(3, PackageID)),
|
||||
ok = initialize_app_file(PackageID, AppStart),
|
||||
MetaList =
|
||||
@ -450,11 +450,11 @@ set_dep2(PackageID) ->
|
||||
Deps = maps:get(deps, Meta),
|
||||
case lists:member(PackageID, Deps) of
|
||||
true -> ok;
|
||||
false -> set_dep(PackageID, Deps, Meta)
|
||||
false -> set_dep3(PackageID, Deps, Meta)
|
||||
end.
|
||||
|
||||
|
||||
-spec set_dep(PackageID, Deps, Meta) -> ok
|
||||
-spec set_dep3(PackageID, Deps, Meta) -> ok
|
||||
when PackageID :: zx:package_id(),
|
||||
Deps :: [zx:package_id()],
|
||||
Meta :: [term()].
|
||||
@ -464,7 +464,7 @@ set_dep2(PackageID) ->
|
||||
%% such a dependency is not already present. Then write the project meta back to its
|
||||
%% file and exit.
|
||||
|
||||
set_dep(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
|
||||
set_dep3(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
|
||||
ExistingPackageIDs = fun({R, N, _}) -> {R, N} == {Realm, Name} end,
|
||||
NewDeps =
|
||||
case lists:partition(ExistingPackageIDs, Deps) of
|
||||
@ -609,18 +609,35 @@ version_up(patch, {Realm, Name, OldVersion = {Major, Minor, Patch}}, OldMeta) ->
|
||||
%% @private
|
||||
%% Turn a target project directory into a package, prompting the user for appropriate
|
||||
%% key selection or generation actions along the way.
|
||||
%% TODO: Add user selection in the case more than one user with private keys exists.
|
||||
|
||||
package(TargetDir) ->
|
||||
ok = log(info, "Packaging ~ts", [TargetDir]),
|
||||
{ok, Meta} = zx_lib:read_project_meta(TargetDir),
|
||||
{Realm, _, _} = maps:get(package_id, Meta),
|
||||
case list_users(Realm) of
|
||||
[] ->
|
||||
UserName = create_user(#user_data{realm = Realm}),
|
||||
package(TargetDir, Meta, UserName);
|
||||
[UserName] ->
|
||||
ok = log(info, "Signing as user ~tp", [UserName]),
|
||||
package(TargetDir, Meta, UserName);
|
||||
UserNames ->
|
||||
UserName = zx_tty:select_string(UserNames),
|
||||
package(TargetDir, Meta, UserName)
|
||||
end.
|
||||
|
||||
package(TargetDir, Meta, UserName) ->
|
||||
{Realm, _, _} = maps:get(package_id, Meta),
|
||||
KeyDir = zx_lib:path(key, Realm),
|
||||
ok = zx_lib:force_dir(KeyDir),
|
||||
Pattern = KeyDir ++ "/*.key.der",
|
||||
Pattern = filename:join(KeyDir, UserName ++ ".*.key.der"),
|
||||
case [filename:basename(F, ".key.der") || F <- filelib:wildcard(Pattern)] of
|
||||
[] ->
|
||||
ok = log(info, "Need to generate key"),
|
||||
KeyID = zx_key:prompt_keygen(),
|
||||
ok = log(info, "No private keys found. Need to generate a keypair."),
|
||||
KeyTag = zx_key:prompt_keygen(),
|
||||
KeyName = UserName ++ "." ++ KeyTag,
|
||||
KeyID = {Realm, KeyName},
|
||||
ok = zx_key:generate_rsa(KeyID),
|
||||
package(KeyID, TargetDir);
|
||||
[KeyName] ->
|
||||
@ -629,13 +646,15 @@ package(TargetDir) ->
|
||||
package(KeyID, TargetDir);
|
||||
KeyNames ->
|
||||
KeyName = zx_tty:select_string(KeyNames),
|
||||
package({Realm, KeyName}, TargetDir)
|
||||
package(TargetDir, Meta, UserName, {Realm, KeyName})
|
||||
end.
|
||||
|
||||
|
||||
-spec package(KeyID, TargetDir) -> no_return()
|
||||
when KeyID :: zx:key_id(),
|
||||
TargetDir :: file:filename().
|
||||
-spec package(TargetDir, Meta, UserName, KeyID) -> zx:outcome()
|
||||
when TargetDir :: file:filename(),
|
||||
Meta :: zx:meta(),
|
||||
UserName :: zx:user_name(),
|
||||
KeyID :: zx:key_id().
|
||||
%% @private
|
||||
%% Accept a KeyPrefix for signing and a TargetDir containing a project to package and
|
||||
%% build a zsp package file ready to be submitted to a repository.
|
||||
@ -645,33 +664,42 @@ package(KeyID, TargetDir) ->
|
||||
PackageID = maps:get(package_id, Meta),
|
||||
true = element(1, PackageID) == element(1, KeyID),
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
ZrpFile = PackageString ++ ".zsp",
|
||||
TgzFile = PackageString ++ ".tgz",
|
||||
ok = zx_lib:halt_if_exists(ZrpFile),
|
||||
ZspFile = PackageString ++ ".zsp",
|
||||
case filelib:is_regular(ZspFile) of
|
||||
true -> {error, "Package file already exists. Aborting", 17};
|
||||
false -> package(KeyID, TargetDir, PackageID, ZspFile)
|
||||
end.
|
||||
|
||||
|
||||
package(KeyID, TargetDir, PackageID, ZspFile) ->
|
||||
ok = remove_binaries(TargetDir),
|
||||
CrashDump = filename:join(TargetDir, "erl_crash.dump"),
|
||||
ok =
|
||||
case filelib:is_regular(CrashDump) of
|
||||
true -> file:delete(CrashDump);
|
||||
false -> ok
|
||||
end,
|
||||
{ok, Everything} = file:list_dir(TargetDir),
|
||||
DotFiles = filelib:wildcard(".*", TargetDir),
|
||||
Ignores = ["lib" | DotFiles],
|
||||
Targets = lists:subtract(Everything, Ignores),
|
||||
Targets = lists:subtract(Everything, DotFiles),
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
ok = file:set_cwd(TargetDir),
|
||||
ok = zx_lib:build(),
|
||||
Modules =
|
||||
[filename:basename(M, ".beam") || M <- filelib:wildcard("*.beam", "ebin")],
|
||||
ok = remove_binaries("."),
|
||||
ok = erl_tar:create(filename:join(CWD, TgzFile), Targets, [compressed]),
|
||||
ok = file:set_cwd(CWD),
|
||||
Grep = "grep -oP '^-module\\(\\K[^)]+' src/* | cut -d: -f2",
|
||||
Modules = string:lexemes(os:cmd(Grep), "\n"),
|
||||
TarGzPath = filename:join(zx_lib:path(tmp), ZspFile ++ ".tgz"),
|
||||
ok = erl_tar:create(TarGzPath, Targets, [compressed]),
|
||||
{ok, TgzBin} = file:read_file(TarGzPath),
|
||||
ok = file:delete(TarGzPath),
|
||||
MetaBin = term_to_binary({PackageID, element(2, KeyID), Modules}),
|
||||
MetaSize = byte_size(MetaBin),
|
||||
SignMe = <<MetaSize:16, MetaBin:MetaSize/binary, TgzBin/binary>>,
|
||||
{ok, Key} = zx_key:load(private, KeyID),
|
||||
{ok, TgzBin} = file:read_file(TgzFile),
|
||||
Sig = public_key:sign(TgzBin, sha512, Key),
|
||||
Add = fun({K, V}, M) -> maps:put(K, V, M) end,
|
||||
FinalMeta = lists:foldl(Add, Meta, [{modules, Modules}, {sig, {KeyID, Sig}}]),
|
||||
ok = file:write_file("zomp.meta", term_to_binary(FinalMeta)),
|
||||
ok = erl_tar:create(ZrpFile, ["zomp.meta", TgzFile]),
|
||||
ok = file:delete(TgzFile),
|
||||
ok = file:delete("zomp.meta"),
|
||||
ok = log(info, "Wrote archive ~ts", [ZrpFile]),
|
||||
halt(0).
|
||||
Sig = public_key:sign(SignMe, sha512, Key),
|
||||
SigSize = byte_size(Sig),
|
||||
ZspData = <<SigSize:24, Sig:SigSize/binary, SignMe/binary>>,
|
||||
ok = file:set_cwd(CWD),
|
||||
ok = file:write_file(ZspFile, ZspData),
|
||||
log(info, "Wrote archive ~ts", [ZspFile]).
|
||||
|
||||
|
||||
-spec remove_binaries(TargetDir) -> ok
|
||||
@ -682,32 +710,18 @@ package(KeyID, TargetDir) ->
|
||||
|
||||
remove_binaries(TargetDir) ->
|
||||
Beams = filelib:wildcard("**/*.{beam,ez}", TargetDir),
|
||||
case [filename:join(TargetDir, Beam) || Beam <- Beams] of
|
||||
[] ->
|
||||
ok;
|
||||
ToDelete ->
|
||||
ok = log(info, "Removing: ~tp", [ToDelete]),
|
||||
lists:foreach(fun file:delete/1, ToDelete)
|
||||
end.
|
||||
ToDelete = [filename:join(TargetDir, Beam) || Beam <- Beams],
|
||||
lists:foreach(fun file:delete/1, ToDelete).
|
||||
|
||||
|
||||
-spec create_plt() -> no_return().
|
||||
%% @private
|
||||
%% Generate a fresh PLT file that includes most basic core applications needed to
|
||||
%% make a resonable estimate of a type system, write the name of the PLT to stdout,
|
||||
%% and exit.
|
||||
|
||||
create_plt() ->
|
||||
ok = build_plt(),
|
||||
halt(0).
|
||||
|
||||
|
||||
-spec build_plt() -> ok.
|
||||
-spec create_plt() -> ok.
|
||||
%% @private
|
||||
%% Build a general plt file for Dialyzer based on the core Erland distro.
|
||||
%% TODO: Make a per-package + dependencies version of this.
|
||||
%% TODO: PLT build options.
|
||||
%% TODO: Meaningful failure messages.
|
||||
|
||||
build_plt() ->
|
||||
create_plt() ->
|
||||
PLT = default_plt(),
|
||||
Template =
|
||||
"dialyzer --build_plt"
|
||||
@ -742,7 +756,7 @@ dialyze() ->
|
||||
ok =
|
||||
case filelib:is_regular(PLT) of
|
||||
true -> log(info, "Using PLT: ~tp", [PLT]);
|
||||
false -> build_plt()
|
||||
false -> create_plt()
|
||||
end,
|
||||
Me = escript:script_name(),
|
||||
EvilTwin = filename:join(zx_lib:path(tmp), filename:basename(Me ++ ".erl")),
|
||||
@ -766,8 +780,43 @@ dialyze() ->
|
||||
|
||||
grow_a_pair() ->
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
KeyID = zx_key:prompt_keygen(),
|
||||
zx_key:generate_rsa(KeyID).
|
||||
case zx_lib:list_realms() of
|
||||
[] ->
|
||||
{error, "No realms configured.", 61};
|
||||
[Realm] ->
|
||||
grow_a_pair(Realm);
|
||||
Realms ->
|
||||
Realm = zx_tty:select_string(Realms),
|
||||
grow_a_pair(Realm)
|
||||
end.
|
||||
|
||||
|
||||
grow_a_pair(Realm) ->
|
||||
Pattern = zx_lib:path(etc, Realm) ++ "*.user",
|
||||
case [filename:basename(F, ".user") || F <- filelib:wildcard(Pattern)] of
|
||||
[] ->
|
||||
{ok, UserName} = create_user(#user_data{realm = Realm}),
|
||||
grow_a_pair(UserName);
|
||||
[UserName] ->
|
||||
grow_a_pair(Realm, UserName);
|
||||
UserNames ->
|
||||
UserName = zx_tty:select_string(UserNames),
|
||||
grow_a_pair(Realm, UserName)
|
||||
end.
|
||||
|
||||
|
||||
grow_a_pair(Realm, UserName) ->
|
||||
KeyTag = zx_key:prompt_keygen(),
|
||||
KeyName = UserName ++ "." ++ KeyTag,
|
||||
case zx_key:generate_rsa({Realm, KeyName}) of
|
||||
ok ->
|
||||
ok;
|
||||
{error, exists} ->
|
||||
ok = io:format("That key name already exists. Try something different.~n"),
|
||||
grow_a_pair(Realm, UserName);
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
-spec drop_key(zx:key_id()) -> ok.
|
||||
@ -1010,13 +1059,14 @@ create_sysop(U = #user_data{username = UserName,
|
||||
end.
|
||||
|
||||
|
||||
-spec create_user() -> zx:outcome().
|
||||
-spec create_user() -> ok.
|
||||
|
||||
create_user() ->
|
||||
create_user(#user_data{}).
|
||||
UserName = create_user(#user_data{}),
|
||||
log(info, "User ~ts created.", [UserName]).
|
||||
|
||||
|
||||
-spec create_user(#user_data{}) -> zx:outcome().
|
||||
-spec create_user(#user_data{}) -> zx:user_name().
|
||||
|
||||
create_user(U = #user_data{realm = none}) ->
|
||||
case pick_realm() of
|
||||
@ -1045,11 +1095,17 @@ create_user(U = #user_data{realm = Realm,
|
||||
"Press a number to select something to change, or [ENTER] to accept.~n",
|
||||
ok = io:format(Instructions, [Realm, UserName, RealName, Email]),
|
||||
case zx_tty:get_input() of
|
||||
"1" -> create_user(U#user_data{realm = none});
|
||||
"2" -> create_user(U#user_data{username = none});
|
||||
"3" -> create_user(U#user_data{realname = none});
|
||||
"4" -> create_user(U#user_data{contact_info = none});
|
||||
"" -> store_user(U);
|
||||
"1" ->
|
||||
create_user(U#user_data{realm = none});
|
||||
"2" ->
|
||||
create_user(U#user_data{username = none});
|
||||
"3" ->
|
||||
create_user(U#user_data{realname = none});
|
||||
"4" ->
|
||||
create_user(U#user_data{contact_info = none});
|
||||
"" ->
|
||||
ok = store_user(U),
|
||||
{ok, UserName};
|
||||
_ ->
|
||||
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
|
||||
create_user(U)
|
||||
@ -1075,6 +1131,15 @@ store_user(#user_data{realm = Realm,
|
||||
log(info, "User ~tp created.", [{Realm, UserName}]).
|
||||
|
||||
|
||||
-spec list_users(Realm) -> UserNames
|
||||
when Realm :: zx:realm(),
|
||||
UserNames :: [zx:user_name()].
|
||||
|
||||
list_users(Realm) ->
|
||||
Pattern = filename:join(zx_lib:path(etc, Realm), "*.user"),
|
||||
[filename:basename(UN, ".user") || UN <- filelib:wildcard(Pattern)].
|
||||
|
||||
|
||||
-spec gen_keys(Realm, KeyNames) -> ok
|
||||
when Realm :: zx:realm(),
|
||||
KeyNames :: [zx:key_names()].
|
||||
@ -1196,16 +1261,17 @@ make_realm_dirs(Realm) ->
|
||||
lists:foreach(Make, Dirs).
|
||||
|
||||
|
||||
-spec configure_zomp() -> ok.
|
||||
|
||||
configure_zomp() ->
|
||||
ZompSettings =
|
||||
[{node, 16},
|
||||
{vampire, 16},
|
||||
{leaf, 256},
|
||||
{listen_port, 11311},
|
||||
{public_port, 11311}],
|
||||
io:format("~tp~n", [ZompSettings]).
|
||||
%% FIXME
|
||||
%-spec configure_zomp() -> ok.
|
||||
%
|
||||
%configure_zomp() ->
|
||||
% ZompSettings =
|
||||
% [{node, 16},
|
||||
% {vampire, 16},
|
||||
% {leaf, 256},
|
||||
% {listen_port, 11311},
|
||||
% {public_port, 11311}],
|
||||
% io:format("~tp~n", [ZompSettings]).
|
||||
|
||||
|
||||
-spec create_realmfile(Realm, Dir) -> ok
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user