package, run, install, etc.

This commit is contained in:
Craig Everett 2018-06-05 11:58:37 +09:00
parent f20f205c4f
commit f630d4d88e
9 changed files with 410 additions and 375 deletions

View File

@ -1,5 +0,0 @@
{realm,"otpr"}.
{username,"zxq9"}.
{realmname,"Craig Everett"}.
{contact_info,{"email","zxq9@zxq9.com"}}.
{keys,["zxq9-root"]}.

View File

@ -9,4 +9,4 @@
zx_conn, zx_conn,
zx_lib, zx_lib,
zx_net]}, zx_net]},
{mod, {zx, []}}]}. {mod, {zx, none}}]}.

View File

@ -100,10 +100,9 @@ do(["list", "deps"]) ->
done(zx_local:list_deps()); done(zx_local:list_deps());
do(["list", "deps", PackageString]) -> do(["list", "deps", PackageString]) ->
done(zx_local:list_deps(PackageString)); done(zx_local:list_deps(PackageString));
do(["import", "zrp", PackageFile]) -> do(["install", PackageFile]) ->
done(zx_daemon:import_zrp(PackageFile)); ok = start(),
do(["install", PackageString]) -> done(zx_daemon:install(PackageFile));
done(zx_daemon:install(PackageString));
do(["set", "dep", PackageString]) -> do(["set", "dep", PackageString]) ->
done(zx_local:set_dep(PackageString)); done(zx_local:set_dep(PackageString));
do(["set", "version", VersionString]) -> do(["set", "version", VersionString]) ->
@ -231,7 +230,8 @@ compatibility_check(Platforms) ->
%% @equiv application:ensure_started(zx). %% @equiv application:ensure_started(zx).
start() -> start() ->
application:ensure_started(zx). ok = application:ensure_started(zx),
zx_daemon:init_connections().
-spec stop() -> ok | {error, Reason :: term()}. -spec stop() -> ok | {error, Reason :: term()}.
@ -251,14 +251,14 @@ stop() ->
-spec start(StartType, StartArgs) -> Result -spec start(StartType, StartArgs) -> Result
when StartType :: normal, when StartType :: normal,
StartArgs :: [], StartArgs :: none,
Result :: {ok, pid()}. Result :: {ok, pid()}.
%% @private %% @private
%% Application callback. Not to be called directly. %% Application callback. Not to be called directly.
start(normal, []) -> start(normal, none) ->
ok = application:ensure_started(inets), ok = application:ensure_started(inets),
zx_daemon:start_link(). zx_sup:start_link().
-spec stop(term()) -> ok. -spec stop(term()) -> ok.
@ -373,36 +373,34 @@ prepare(PackageID, Meta, Dir, RunArgs) ->
Deps = maps:get(deps, Meta), Deps = maps:get(deps, Meta),
NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end, NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end,
Needed = lists:filter(NotInstalled, Deps), Needed = lists:filter(NotInstalled, Deps),
Pending = lists:map(fun zx_daemon:fetch/1, Needed), ok = lists:foreach(fun install/1, Needed),
case await_fetches(Pending) of ok = lists:foreach(fun build/1, Needed),
ok -> execute(Type, PackageID, Meta, Dir, RunArgs).
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().
{error, Errors} -> %% @private
error_exit("Failed package fetches: ~tp", [Errors], ?LINE) %% 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. 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() -spec execute(Type, PackageID, Meta, Dir, RunArgs) -> no_return()
when Type :: app | lib, when Type :: app | lib,
PackageID :: package_id(), PackageID :: package_id(),
@ -518,7 +516,7 @@ resolve_installed_version({Realm, Name, Version}) ->
resolve_installed_version(PackageDir, Version) -> resolve_installed_version(PackageDir, Version) ->
DirStrings = filelib:wildcard("*", PackageDir), 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). zx_lib:find_latest_compatible(Version, Versions).
@ -540,14 +538,15 @@ build(PackageID) ->
file:set_cwd(CWD). file:set_cwd(CWD).
-spec ensure_package_dirs(package_id()) -> ok. %% FIXME
%% @private %-spec ensure_package_dirs(package_id()) -> ok.
%% Procedure to guarantee that directory locations necessary for the indicated app to %%% @private
%% run have been created or halt execution. %%% 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]], %ensure_package_dirs(PackageID) ->
lists:foreach(fun zx_lib:force_dir/1, Dirs). % Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]],
% lists:foreach(fun zx_lib:force_dir/1, Dirs).

View File

@ -98,14 +98,15 @@ list_resigns(Realm) ->
submit(ZspPath) -> submit(ZspPath) ->
{ok, ZspBin} = file:read_file(ZspPath), {ok, ZspBin} = file:read_file(ZspPath),
<<SigSize:24, Sig:SigSize/binary, Signed/binary>> = ZspBin, <<SigSize:24, Sig:SigSize/binary, Signed/binary>> = ZspBin,
<<MetaSize:16, MetaBin:MetaSize/binary, TarGz/binary>> = Signed, <<MetaSize:16, MetaBin:MetaSize/binary, _/binary>> = Signed,
{ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin), {ok, {PackageID, SigKeyName, _}} = zx_lib:b_to_ts(MetaBin),
{ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}), {ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
true = zx_key:verify(Signed, Sig, PubKey), true = zx_key:verify(Signed, Sig, PubKey),
{ok, Socket} = connect_auth(element(1, PackageID)),
ok = send(Socket, {submit, PackageID}), ok = send(Socket, {submit, PackageID}),
ok = recv_or_die(Socket), ok = recv_or_die(Socket),
ok = gen_tcp:send(Socket, ZspBin), 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), Outcome = recv_or_die(Socket),
log(info, "Response: ~tp", [Outcome]), log(info, "Response: ~tp", [Outcome]),
disconnect(Socket). disconnect(Socket).

View File

@ -10,7 +10,7 @@
-copyright("Craig Everett <zxq9@zxq9.com>"). -copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0"). -license("GPL-3.0").
-export([start_conn/2]). -export([start_conn/1]).
-export([start_link/0]). -export([start_link/0]).
-export([init/1]). -export([init/1]).
@ -18,9 +18,8 @@
%%% Interface Functions %%% Interface Functions
-spec start_conn(Host, Serial) -> Result -spec start_conn(Host) -> Result
when Host :: zx:host(), when Host :: zx:host(),
Serial :: zx:serial(),
Result :: {ok, pid()} Result :: {ok, pid()}
| {error, Reason}, | {error, Reason},
Reason :: term(). Reason :: term().
@ -28,8 +27,8 @@
%% Start an upstream connection handler. %% Start an upstream connection handler.
%% (Should only be called from zx_conn). %% (Should only be called from zx_conn).
start_conn(Host, Serial) -> start_conn(Host) ->
supervisor:start_child(?MODULE, [Host, Serial]). supervisor:start_child(?MODULE, [Host]).

View File

@ -147,11 +147,10 @@
-export([pass_meta/3, -export([pass_meta/3,
subscribe/1, unsubscribe/1, subscribe/1, unsubscribe/1,
list/0, list/1, list/2, list/3, latest/1, list/0, list/1, list/2, list/3, latest/1,
fetch/1, verify_key/1, verify_key/1, fetch/1, install/1,
install/1, import_zsp/1,
pending/1, packagers/1, maintainers/1, sysops/1]). pending/1, packagers/1, maintainers/1, sysops/1]).
-export([report/1, result/2, notify/2]). -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, -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]). code_change/3, terminate/2]).
@ -189,14 +188,12 @@
-record(rmeta, -record(rmeta,
{revision = 0 :: non_neg_integer(), {serial = 0 :: non_neg_integer(),
serial = 0 :: non_neg_integer(),
prime = {"zomp.tsuriai.jp", 11311} :: zx:host(), prime = {"zomp.tsuriai.jp", 11311} :: zx:host(),
private = [] :: [zx:host()], private = [] :: [zx:host()],
mirrors = queue:new() :: queue:queue(zx:host()), mirrors = queue:new() :: queue:queue(zx:host()),
realm_keys = [] :: [zx:key_meta()], key = [] :: zx:key_name(),
package_keys = [] :: [zx:key_meta()], sysop = none :: zx:user_name(),
sysops = [] :: [zx:sysop_meta()],
assigned = none :: none | pid(), assigned = none :: none | pid(),
available = [] :: [pid()]}). available = [] :: [pid()]}).
@ -450,25 +447,6 @@ latest({Realm, Name, Version}) ->
request({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} -spec verify_key(KeyID) -> {ok, RequestID}
when KeyID :: zx:key_id(), when KeyID :: zx:key_id(),
RequestID :: id(). RequestID :: id().
@ -486,24 +464,25 @@ verify_key({Realm, KeyName}) ->
request({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 %% @doc
%% Install a package from a local file. %% Install a package from a local file.
import_zsp(Path) -> install(Path) ->
gen_server:call(?MODULE, {import_zsp, Path}). gen_server:call(?MODULE, {install, Path}).
-spec install(PackageString :: string()) -> zx:outcome(). -spec fetch(PackageString :: string()) -> {ok, id()}.
%% @doc %% @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 case zx_lib:package_id(PackageString) of
{ok, PackageID} -> {ok, PackageID} ->
gen_server:call(?MODULE, {install, PackageID}); gen_server:call(?MODULE, {fetch, PackageID});
{error, invalid_package_string} -> {error, invalid_package_string} ->
{error, "Invalid package string. Try again.", 22} {error, "Invalid package string.", 22}
end. end.
@ -632,63 +611,13 @@ start_link() ->
%% TODO: Implement lockfile checking and master lock acquisition. %% TODO: Implement lockfile checking and master lock acquisition.
init(none) -> init(none) ->
Blank = blank_state(), {ok, #s{}}.
{ok, MX, CX} = init_connections(),
State = Blank#s{mx = MX, cx = CX},
{ok, State}.
-spec blank_state() -> state(). -spec init_connections() -> ok.
%% @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.
init_connections() -> init_connections() ->
CX = cx_load(), gen_server:cast(?MODULE, init_connections).
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}.
@ -718,11 +647,16 @@ handle_call({request, Action}, From, State = #s{id = ID}) ->
NextState = do_request(Requestor, Action, State#s{id = NewID}), NextState = do_request(Requestor, Action, State#s{id = NewID}),
NewState = eval_queue(NextState), NewState = eval_queue(NextState),
{noreply, NewState}; {noreply, NewState};
handle_call({install, PackageID}, _, State) -> handle_call({fetch, PackageID}, From, State = #s{id = ID}) ->
{Result, NewState} = do_install(PackageID, State), NewID = ID + 1,
{reply, Result, NewState}; _ = gen_server:reply(From, {ok, NewID}),
handle_call({import_zsp, Path}, _, State) -> 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), Result = do_import_zsp(Path),
NewState = eval_queue(State),
{reply, Result, NewState}; {reply, Result, NewState};
handle_call(Unexpected, From, State) -> handle_call(Unexpected, From, State) ->
ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]), 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), ok = do_notify(Conn, Package, Update, State),
NewState = eval_queue(State), NewState = eval_queue(State),
{noreply, NewState}; {noreply, NewState};
handle_cast(init_connections, State) ->
NewState = init_connections(State),
{noreply, NewState};
handle_cast(stop, State) -> handle_cast(stop, State) ->
{stop, normal, State}; {stop, normal, State};
handle_cast(Unexpected, State) -> handle_cast(Unexpected, State) ->
@ -942,6 +879,49 @@ dequeue(Pending) ->
end. 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} -spec ensure_connections(Realms, MX, CX) -> {NewMX, NewCX}
when Realms :: [zx:realm()], when Realms :: [zx:realm()],
MX :: monitor_index(), MX :: monitor_index(),
@ -997,11 +977,14 @@ reassign_conns([], CX, Unassigned) ->
do_result(ID, Result, State = #s{requests = Requests, dropped = Dropped, mx = MX}) -> do_result(ID, Result, State = #s{requests = Requests, dropped = Dropped, mx = MX}) ->
{NewDropped, NewRequests, NewMX} = {NewDropped, NewRequests, NewMX} =
case maps:take(ID, Requests) of 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 = element(1, Request),
Requestor ! {z_result, ID, Result}, Requestor ! {z_result, ID, Result},
NextMX = mx_del_monitor(Requestor, {requestor, ID}, MX), NextMX = mx_del_monitor(Requestor, {requestor, ID}, MX),
{Dropped, NextRequests, NextMX}; {Dropped, Rest, NextMX};
error -> error ->
NextDropped = handle_orphan_result(ID, Result, Dropped), NextDropped = handle_orphan_result(ID, Result, Dropped),
{NextDropped, Requests, MX} {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}. 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 -spec handle_orphan_result(ID, Result, Dropped) -> NewDropped
when ID :: id(), when ID :: id(),
Result :: result(), Result :: result(),
@ -1237,22 +1238,32 @@ drop_requests(ReqIDs, Dropped, Requests) ->
lists:fold(Partition, {Dropped, Requests}, ReqIDs). lists:fold(Partition, {Dropped, Requests}, ReqIDs).
-spec install(PackageID, State) -> {Result, NewState} -spec do_fetch(PackageID, Requestor, State) -> NewState
when PackageID :: zx:package_id(), when PackageID :: zx:package_id(),
Requestor :: pid(),
State :: state(), State :: state(),
Result :: zx:outcome(),
NewState :: state(). NewState :: state().
%% @private %% @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) -> do_fetch(PackageID, Requestor, State = #s{id = ID}) ->
Path = zx_lib:zxp_path(PackageID), Path = zx_lib:zsp_path(PackageID),
case file:is_regular(Path) of case file:read_file(Path) of
true -> {ok, Bin} ->
do_install(PackageID, Path); case do_import_package(Bin) of
false -> ok ->
ok = do_fetch(PackageID), Requestor ! {z_result, ID, ok},
do_install(PackageID, Path) {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. end.
@ -1260,22 +1271,35 @@ install(PackageID, State) ->
%% @private %% @private
%% Dealing with data from the (probably local) filesystem can fail in a bajillion ways %% 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 %% 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) -> do_import_zsp(Path) ->
{Pid, Mon} = spawn_monitor(fun() -> actually_import(Path) end), {Pid, Mon} = spawn_monitor(fun() -> import_from_path(Path) end),
receive receive
{Pid, Outcome} -> {Pid, Outcome} ->
true = demonitor(Mon, [flush]), true = demonitor(Mon, [flush]),
Outcome; Outcome;
{'DOWN', Pid, process, Mon, Info} -> {'DOWN', Pid, process, Mon, Info} ->
{error, Info}; {error, Info}
after 5000 -> after 5000 ->
{error, timeout} {error, timeout}
end. 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(). when ZspPath :: file:filename().
%% @private %% @private
%% The happy path of .zsp installation. %% 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 %% 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. %% identicalish segements of functions a few places then I'll break things apart.
actually_import(ZspPath) -> import_from_path(ZspPath) ->
{ok, Bin = <<Size:24, Sig:Size/binary, Signed/binary>>} = file:read_file(ZspPath), {ok, Bin} = file:read_file(ZspPath),
<<MetaSize:16, MetaBin:MetaSize/binary, TarGz/binary>> = Signed, import_package(Bin).
{ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin),
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}), {ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}),
true = zx_key:verify(Signed, Sig, PubKey), true = zx_key:verify(Signed, Sig, PubKey),
ok = file:write_file(zx_lib:zsp_path(PackageID), Bin), ok = file:write_file(zx_lib:zsp_path(PackageID), Bin),
@ -1313,8 +1340,8 @@ actually_import(ZspPath) ->
ok = filelib:ensure_dir(Destination), ok = filelib:ensure_dir(Destination),
ok = zx_lib:rm_rf(Destination), ok = zx_lib:rm_rf(Destination),
ok = file:make_dir(Destination), ok = file:make_dir(Destination),
ok = erl_tar:extract(TarGZ, [{cwd, Destination}]), Result = erl_tar:extract({binary, TarGZ}, [{cwd, Destination}, compressed]),
zx_daemon ! {self(), ok}. 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). %% where any number of wild things might be going on in the user's filesystem).
cx_populate() -> cx_populate() ->
Pattern = filename:join([zx_lib:path(etc), "*", "realm.conf"]), Realms = zx_lib:list_realms(),
case filelib:wildcard(Pattern) of CX = lists:foldl(fun cx_populate/2, [], Realms),
[] -> {error, no_realms}; {ok, CX}.
RealmFiles -> {ok, cx_populate(RealmFiles, [])}
end.
-spec cx_populate(RealmFiles, Realms) -> NewRealms -spec cx_populate(Realms, CX) -> NewCX
when RealmFiles :: file:filename(), when Realms :: [zx:realm()],
Realms :: [{zx:realm(), realm_meta()}], CX :: [{zx:realm(), realm_meta()}],
NewRealms :: [{zx:realm(), realm_meta()}]. NewCX :: [{zx:realm(), realm_meta()}].
%% @private %% @private
%% Pack an initially empty conn_index() with realm meta and host cache data. %% 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 %% Should not halt on a corrupted, missing, malformed, etc. realm file but will log
%% any loading errors. %% any loading errors.
cx_populate([File | Files], Realms) -> cx_populate(Realm, CX) ->
NewRealms = case zx_lib:load_realm_conf(Realm) of
case file:consult(File) of {ok, Meta} ->
{ok, Meta} -> Record = cx_load_realm_meta(Meta),
Realm = cx_load_realm_meta(Meta), [{Realm, Record} | CX];
[Realm | Realms]; {error, Reason} ->
{error, Reason} -> Message = "Loading realm ~tp failed with: ~tp. Skipping...",
Message = "Loading realm file ~tp failed with: ~tp. Skipping...", ok = log(warning, Message, [Realm, Reason]),
ok = log(warning, Message, [File, Reason]), CX
Realms end.
end,
cx_populate(Files, NewRealms);
cx_populate([], Realms) ->
Realms.
-spec cx_load_realm_meta(Meta) -> Result -spec cx_load_realm_meta(Meta) -> Result
@ -1514,20 +1535,12 @@ cx_populate([], Realms) ->
%% This function MUST adhere to the realmfile definition found at. %% This function MUST adhere to the realmfile definition found at.
cx_load_realm_meta(Meta) -> cx_load_realm_meta(Meta) ->
{realm, Realm} = lists:keyfind(realm, 1, Meta), Realm = maps:get(realm, 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),
Basic = Basic =
#rmeta{revision = Revision, #rmeta{prime = maps:get(prime, Meta),
prime = Prime, sysop = maps:get(sysop, Meta),
realm_keys = RealmKeys, key = maps:get(key, Meta)},
package_keys = PackageKeys, cx_load_cache(Realm, Basic).
sysops = Sysops},
Complete = cx_load_cache(Realm, Basic),
{Realm, Complete}.
-spec cx_load_cache(Realm, Basic) -> Complete -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}}; {ok, Next, Meta#rmeta{mirrors = NewMirrors}};
{empty, Mirrors} -> {empty, Mirrors} ->
Enqueue = fun(H, Q) -> queue:in(H, Q) end, 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}} {prime, Prime, Meta#rmeta{mirrors = NewMirrors}}
end. end.

View File

@ -31,15 +31,15 @@ ensure_keypair(KeyID = {Realm, KeyName}) ->
true; true;
{false, true} -> {false, true} ->
Format = "Public key ~tp/~tp cannot be found", 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}; {error, Message, 2};
{true, false} -> {true, false} ->
Format = "Private key ~tp/~tp cannot be found", 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}; {error, Message, 2};
{false, false} -> {false, false} ->
Format = "Key pair ~tp/~tp cannot be found", 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} {error, Message, 2}
end. end.
@ -76,49 +76,47 @@ path(private, {Realm, KeyName}) ->
prompt_keygen() -> prompt_keygen() ->
Message = Message =
"~n Enter a name for your new keys.~n~n" "~nKEY NAME~n"
" Valid names must start with a lower-case letter, and can include~n" "Enter a name for your new key pair.~n"
" only lower-case letters, numbers, and periods, but no series of~n" "Valid names must start with a lower-case letter, and can include "
" consecutive periods. (That is: [a-z0-9\\.])~n~n" "only lower-case letters, numbers, and underscores, but no series of "
" To designate the key as realm-specific, enter the realm name and~n" "consecutive underscores. (That is: [a-z0-9_])~n"
" key name separated by a space.~n~n" " Example: my_key~n",
" Example: some.realm my.key~n",
ok = io:format(Message), ok = io:format(Message),
Input = zx_tty:get_input(), Input = zx_tty:get_input(),
{Realm, KeyName} = case zx_lib:valid_lower0_9(Input) of
case string:lexemes(Input, " ") of true ->
[R, K] -> {R, K}; Input;
[K] -> {"otpr", K} false ->
end, ok = io:format("Bad key name ~tp. Try again.~n", [Input]),
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"),
prompt_keygen() prompt_keygen()
end. end.
-spec generate_rsa(KeyID) -> Result -spec generate_rsa(KeyID) -> Result
when KeyID :: zx:key_id(), when KeyID :: zx:key_id(),
Result :: ok Result :: ok
| {error, keygen_fail}. | {error, Reason},
Reason :: keygen_fail
| exists.
%% @private %% @private
%% Generate an RSA keypair and write them in der format to the current directory, using %% Generate an RSA keypair and write them in der format to the current directory, using
%% filenames derived from Prefix. %% filenames derived from Prefix.
%% NOTE: The current version of this command is likely to only work on a unix system. %% NOTE: The current version of this command is likely to only work on a unix system.
generate_rsa(KeyID = {Realm, KeyName}) -> 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), KeyFile = path(private, KeyID),
PubFile = path(public, 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]), ok = log(info, "Generating ~p and ~p. Please be patient...", [KeyFile, PubFile]),
case gen_p_key(KeyFile) of case gen_p_key(KeyFile) of
ok -> ok ->
@ -144,7 +142,7 @@ generate_rsa(KeyID = {Realm, KeyName}) ->
-spec gen_p_key(KeyFile) -> Result -spec gen_p_key(KeyFile) -> Result
when KeyFile :: file:filename() when KeyFile :: file:filename(),
Result :: ok Result :: ok
| {error, no_ssl}. | {error, no_ssl}.
%% @private %% @private
@ -179,16 +177,21 @@ gen_p_key(KeyFile) ->
%% way. %% way.
der_to_pem(KeyFile, PemFile) -> der_to_pem(KeyFile, PemFile) ->
Command = case openssl() of
io_lib:format("~ts rsa" {ok, OpenSSL} ->
" -inform DER" Command =
" -in ~ts" io_lib:format("~ts rsa"
" -outform PEM" " -inform DER"
" -pubout" " -in ~ts"
" -out ~ts", " -outform PEM"
[openssl(), KeyFile, PemFile]), " -pubout"
Out = os:cmd(Command), " -out ~ts",
io:format(Out). [OpenSSL, KeyFile, PemFile]),
Out = os:cmd(Command),
io:format(Out);
Error ->
Error
end.
-spec check_key(KeyFile, PubFile) -> Result -spec check_key(KeyFile, PubFile) -> Result
@ -230,7 +233,7 @@ openssl() ->
{error, no_ssl}; {error, no_ssl};
Path -> Path ->
log(info, "OpenSSL executable found at: ~ts", [Path]), log(info, "OpenSSL executable found at: ~ts", [Path]),
OpenSSL {ok, OpenSSL}
end. end.
@ -266,29 +269,3 @@ load(Type, KeyID) ->
verify(Data, Signature, PubKey) -> verify(Data, Signature, PubKey) ->
public_key:verify(Data, sha512, 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).

View File

@ -589,7 +589,7 @@ namify_zsp(PackageID) ->
-spec zsp_path(zx:package_id()) -> file:filename(). -spec zsp_path(zx:package_id()) -> file:filename().
zsp_path({Realm, _, _}) -> zsp_path(PackageID = {Realm, _, _}) ->
filename:join(path(zsp, Realm), namify_zsp(PackageID)). filename:join(path(zsp, Realm), namify_zsp(PackageID)).
@ -750,18 +750,3 @@ b_to_ts(Binary) ->
catch catch
error:badarg -> error error:badarg -> error
end. 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).

View File

@ -73,14 +73,14 @@ initialize3(Type, PackageID) ->
case package_exists(PackageID) of case package_exists(PackageID) of
false -> false ->
Prefix = ask_prefix(), Prefix = ask_prefix(),
initialize(Type, PackageID, Prefix); initialize4(Type, PackageID, Prefix);
true -> true ->
Message = "Package already exists. Try another.", Message = "Package already exists. Try another.",
{error, Message, 17} {error, Message, 17}
end. end.
initialize(app, PackageID, Prefix) -> initialize4(app, PackageID, Prefix) ->
Instructions = Instructions =
"The OTP application controller has to know what module to call start/2 on to " "The OTP application controller has to know what module to call start/2 on to "
"start your program.~n" "start your program.~n"
@ -93,23 +93,23 @@ initialize(app, PackageID, Prefix) ->
ok = io:format(Instructions), ok = io:format(Instructions),
case zx_tty:get_input() of case zx_tty:get_input() of
"" -> "" ->
initialize(lib, PackageID, Prefix, none); initialize5(lib, PackageID, Prefix, none);
String -> String ->
case zx_lib:valid_lower0_9(String) of case zx_lib:valid_lower0_9(String) of
true -> true ->
AppStart = {list_to_atom(String), []}, AppStart = {list_to_atom(String), []},
initialize(app, PackageID, Prefix, AppStart); initialize5(app, PackageID, Prefix, AppStart);
false -> false ->
Message = "The name \"~ts\" doesn't seem valid. Try \"[a-z_]*\".~n", Message = "The name \"~ts\" doesn't seem valid. Try \"[a-z_]*\".~n",
ok = io:format(Message, String), ok = io:format(Message, String),
initialize(app, PackageID, Prefix) initialize4(app, PackageID, Prefix)
end end
end; end;
initialize(lib, PackageID, Prefix) -> initialize4(lib, PackageID, Prefix) ->
initialize(lib, PackageID, Prefix, none). initialize5(lib, PackageID, Prefix, none).
initialize(Type, PackageID, Prefix, AppStart) -> initialize5(Type, PackageID, Prefix, AppStart) ->
ok = update_source_vsn(element(3, PackageID)), ok = update_source_vsn(element(3, PackageID)),
ok = initialize_app_file(PackageID, AppStart), ok = initialize_app_file(PackageID, AppStart),
MetaList = MetaList =
@ -450,11 +450,11 @@ set_dep2(PackageID) ->
Deps = maps:get(deps, Meta), Deps = maps:get(deps, Meta),
case lists:member(PackageID, Deps) of case lists:member(PackageID, Deps) of
true -> ok; true -> ok;
false -> set_dep(PackageID, Deps, Meta) false -> set_dep3(PackageID, Deps, Meta)
end. end.
-spec set_dep(PackageID, Deps, Meta) -> ok -spec set_dep3(PackageID, Deps, Meta) -> ok
when PackageID :: zx:package_id(), when PackageID :: zx:package_id(),
Deps :: [zx:package_id()], Deps :: [zx:package_id()],
Meta :: [term()]. Meta :: [term()].
@ -464,7 +464,7 @@ set_dep2(PackageID) ->
%% 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_dep3(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
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
@ -609,18 +609,35 @@ version_up(patch, {Realm, Name, OldVersion = {Major, Minor, Patch}}, OldMeta) ->
%% @private %% @private
%% Turn a target project directory into a package, prompting the user for appropriate %% Turn a target project directory into a package, prompting the user for appropriate
%% key selection or generation actions along the way. %% 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) -> package(TargetDir) ->
ok = log(info, "Packaging ~ts", [TargetDir]), ok = log(info, "Packaging ~ts", [TargetDir]),
{ok, Meta} = zx_lib:read_project_meta(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), {Realm, _, _} = maps:get(package_id, Meta),
KeyDir = zx_lib:path(key, Realm), KeyDir = zx_lib:path(key, Realm),
ok = zx_lib:force_dir(KeyDir), 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 case [filename:basename(F, ".key.der") || F <- filelib:wildcard(Pattern)] of
[] -> [] ->
ok = log(info, "Need to generate key"), ok = log(info, "No private keys found. Need to generate a keypair."),
KeyID = zx_key:prompt_keygen(), KeyTag = zx_key:prompt_keygen(),
KeyName = UserName ++ "." ++ KeyTag,
KeyID = {Realm, KeyName},
ok = zx_key:generate_rsa(KeyID), ok = zx_key:generate_rsa(KeyID),
package(KeyID, TargetDir); package(KeyID, TargetDir);
[KeyName] -> [KeyName] ->
@ -629,13 +646,15 @@ package(TargetDir) ->
package(KeyID, TargetDir); package(KeyID, TargetDir);
KeyNames -> KeyNames ->
KeyName = zx_tty:select_string(KeyNames), KeyName = zx_tty:select_string(KeyNames),
package({Realm, KeyName}, TargetDir) package(TargetDir, Meta, UserName, {Realm, KeyName})
end. end.
-spec package(KeyID, TargetDir) -> no_return() -spec package(TargetDir, Meta, UserName, KeyID) -> zx:outcome()
when KeyID :: zx:key_id(), when TargetDir :: file:filename(),
TargetDir :: file:filename(). Meta :: zx:meta(),
UserName :: zx:user_name(),
KeyID :: zx:key_id().
%% @private %% @private
%% Accept a KeyPrefix for signing and a TargetDir containing a project to package and %% 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. %% 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), PackageID = maps:get(package_id, Meta),
true = element(1, PackageID) == element(1, KeyID), true = element(1, PackageID) == element(1, KeyID),
{ok, PackageString} = zx_lib:package_string(PackageID), {ok, PackageString} = zx_lib:package_string(PackageID),
ZrpFile = PackageString ++ ".zsp", ZspFile = PackageString ++ ".zsp",
TgzFile = PackageString ++ ".tgz", case filelib:is_regular(ZspFile) of
ok = zx_lib:halt_if_exists(ZrpFile), true -> {error, "Package file already exists. Aborting", 17};
false -> package(KeyID, TargetDir, PackageID, ZspFile)
end.
package(KeyID, TargetDir, PackageID, ZspFile) ->
ok = remove_binaries(TargetDir), 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), {ok, Everything} = file:list_dir(TargetDir),
DotFiles = filelib:wildcard(".*", TargetDir), DotFiles = filelib:wildcard(".*", TargetDir),
Ignores = ["lib" | DotFiles], Targets = lists:subtract(Everything, DotFiles),
Targets = lists:subtract(Everything, Ignores),
{ok, CWD} = file:get_cwd(), {ok, CWD} = file:get_cwd(),
ok = file:set_cwd(TargetDir), ok = file:set_cwd(TargetDir),
ok = zx_lib:build(), Grep = "grep -oP '^-module\\(\\K[^)]+' src/* | cut -d: -f2",
Modules = Modules = string:lexemes(os:cmd(Grep), "\n"),
[filename:basename(M, ".beam") || M <- filelib:wildcard("*.beam", "ebin")], TarGzPath = filename:join(zx_lib:path(tmp), ZspFile ++ ".tgz"),
ok = remove_binaries("."), ok = erl_tar:create(TarGzPath, Targets, [compressed]),
ok = erl_tar:create(filename:join(CWD, TgzFile), Targets, [compressed]), {ok, TgzBin} = file:read_file(TarGzPath),
ok = file:set_cwd(CWD), 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, Key} = zx_key:load(private, KeyID),
{ok, TgzBin} = file:read_file(TgzFile), Sig = public_key:sign(SignMe, sha512, Key),
Sig = public_key:sign(TgzBin, sha512, Key), SigSize = byte_size(Sig),
Add = fun({K, V}, M) -> maps:put(K, V, M) end, ZspData = <<SigSize:24, Sig:SigSize/binary, SignMe/binary>>,
FinalMeta = lists:foldl(Add, Meta, [{modules, Modules}, {sig, {KeyID, Sig}}]), ok = file:set_cwd(CWD),
ok = file:write_file("zomp.meta", term_to_binary(FinalMeta)), ok = file:write_file(ZspFile, ZspData),
ok = erl_tar:create(ZrpFile, ["zomp.meta", TgzFile]), log(info, "Wrote archive ~ts", [ZspFile]).
ok = file:delete(TgzFile),
ok = file:delete("zomp.meta"),
ok = log(info, "Wrote archive ~ts", [ZrpFile]),
halt(0).
-spec remove_binaries(TargetDir) -> ok -spec remove_binaries(TargetDir) -> ok
@ -682,32 +710,18 @@ package(KeyID, TargetDir) ->
remove_binaries(TargetDir) -> remove_binaries(TargetDir) ->
Beams = filelib:wildcard("**/*.{beam,ez}", TargetDir), Beams = filelib:wildcard("**/*.{beam,ez}", TargetDir),
case [filename:join(TargetDir, Beam) || Beam <- Beams] of ToDelete = [filename:join(TargetDir, Beam) || Beam <- Beams],
[] -> lists:foreach(fun file:delete/1, ToDelete).
ok;
ToDelete ->
ok = log(info, "Removing: ~tp", [ToDelete]),
lists:foreach(fun file:delete/1, ToDelete)
end.
-spec create_plt() -> no_return(). -spec create_plt() -> ok.
%% @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.
%% @private %% @private
%% Build a general plt file for Dialyzer based on the core Erland distro. %% Build a general plt file for Dialyzer based on the core Erland distro.
%% TODO: Make a per-package + dependencies version of this. %% TODO: Make a per-package + dependencies version of this.
%% TODO: PLT build options.
%% TODO: Meaningful failure messages.
build_plt() -> create_plt() ->
PLT = default_plt(), PLT = default_plt(),
Template = Template =
"dialyzer --build_plt" "dialyzer --build_plt"
@ -742,7 +756,7 @@ dialyze() ->
ok = ok =
case filelib:is_regular(PLT) of case filelib:is_regular(PLT) of
true -> log(info, "Using PLT: ~tp", [PLT]); true -> log(info, "Using PLT: ~tp", [PLT]);
false -> build_plt() false -> create_plt()
end, end,
Me = escript:script_name(), Me = escript:script_name(),
EvilTwin = filename:join(zx_lib:path(tmp), filename:basename(Me ++ ".erl")), EvilTwin = filename:join(zx_lib:path(tmp), filename:basename(Me ++ ".erl")),
@ -766,8 +780,43 @@ dialyze() ->
grow_a_pair() -> grow_a_pair() ->
ok = file:set_cwd(zx_lib:zomp_dir()), ok = file:set_cwd(zx_lib:zomp_dir()),
KeyID = zx_key:prompt_keygen(), case zx_lib:list_realms() of
zx_key:generate_rsa(KeyID). [] ->
{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. -spec drop_key(zx:key_id()) -> ok.
@ -1010,13 +1059,14 @@ create_sysop(U = #user_data{username = UserName,
end. end.
-spec create_user() -> zx:outcome(). -spec create_user() -> ok.
create_user() -> 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}) -> create_user(U = #user_data{realm = none}) ->
case pick_realm() of 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", "Press a number to select something to change, or [ENTER] to accept.~n",
ok = io:format(Instructions, [Realm, UserName, RealName, Email]), ok = io:format(Instructions, [Realm, UserName, RealName, Email]),
case zx_tty:get_input() of case zx_tty:get_input() of
"1" -> create_user(U#user_data{realm = none}); "1" ->
"2" -> create_user(U#user_data{username = none}); create_user(U#user_data{realm = none});
"3" -> create_user(U#user_data{realname = none}); "2" ->
"4" -> create_user(U#user_data{contact_info = none}); create_user(U#user_data{username = none});
"" -> store_user(U); "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"), ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
create_user(U) create_user(U)
@ -1075,6 +1131,15 @@ store_user(#user_data{realm = Realm,
log(info, "User ~tp created.", [{Realm, UserName}]). 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 -spec gen_keys(Realm, KeyNames) -> ok
when Realm :: zx:realm(), when Realm :: zx:realm(),
KeyNames :: [zx:key_names()]. KeyNames :: [zx:key_names()].
@ -1196,16 +1261,17 @@ make_realm_dirs(Realm) ->
lists:foreach(Make, Dirs). lists:foreach(Make, Dirs).
-spec configure_zomp() -> ok. %% FIXME
%-spec configure_zomp() -> ok.
configure_zomp() -> %
ZompSettings = %configure_zomp() ->
[{node, 16}, % ZompSettings =
{vampire, 16}, % [{node, 16},
{leaf, 256}, % {vampire, 16},
{listen_port, 11311}, % {leaf, 256},
{public_port, 11311}], % {listen_port, 11311},
io:format("~tp~n", [ZompSettings]). % {public_port, 11311}],
% io:format("~tp~n", [ZompSettings]).
-spec create_realmfile(Realm, Dir) -> ok -spec create_realmfile(Realm, Dir) -> ok