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_conn,
|
||||||
zx_lib,
|
zx_lib,
|
||||||
zx_net]},
|
zx_net]},
|
||||||
{mod, {zx, []}}]}.
|
{mod, {zx, none}}]}.
|
||||||
|
|||||||
@ -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,34 +373,32 @@ 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),
|
|
||||||
case await_fetches(Pending) of
|
|
||||||
ok ->
|
|
||||||
ok = lists:foreach(fun install/1, Needed),
|
ok = lists:foreach(fun install/1, Needed),
|
||||||
ok = lists:foreach(fun build/1, Needed),
|
ok = lists:foreach(fun build/1, Needed),
|
||||||
execute(Type, PackageID, Meta, Dir, RunArgs);
|
execute(Type, PackageID, Meta, Dir, RunArgs).
|
||||||
{error, Errors} ->
|
|
||||||
error_exit("Failed package fetches: ~tp", [Errors], ?LINE)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
await_fetches([]) -> ok;
|
-spec install(PackageString :: string()) -> zx:outcome().
|
||||||
await_fetches(Pending) -> await_fetches(Pending, []).
|
%% @private
|
||||||
|
%% Installs a package from upstream.
|
||||||
|
|
||||||
|
install(PackageString) ->
|
||||||
|
{ok, ID} = zx_daemon:install(PackageString),
|
||||||
|
install(PackageString, ID).
|
||||||
|
|
||||||
|
|
||||||
await_fetches([], []) ->
|
install(PackageString, ID) ->
|
||||||
ok;
|
|
||||||
await_fetches([], Errors) ->
|
|
||||||
{error, Errors};
|
|
||||||
await_fetches(Pending, Errors) ->
|
|
||||||
{NewPending, NewErrors} =
|
|
||||||
receive
|
receive
|
||||||
{z_reply, ID, ok} ->
|
{z_result, ID, done} ->
|
||||||
{lists:delete(ID, Pending), Errors};
|
ok;
|
||||||
{z_reply, ID, {error, Package, Reason}} ->
|
{z_result, ID, {hops, Count}} ->
|
||||||
{lists:delete(ID, Pending), [{Package, Reason} | Errors]}
|
ok = log(info, "~ts ~w hops away.", [PackageString, Count]),
|
||||||
end,
|
install(PackageString, ID);
|
||||||
await_fetches(NewPending, NewErrors).
|
{z_result, ID, {error, Reason}} ->
|
||||||
|
{error, Reason, 1}
|
||||||
|
after 60000 ->
|
||||||
|
{error, timeout, 62}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec execute(Type, PackageID, Meta, Dir, RunArgs) -> no_return()
|
-spec execute(Type, PackageID, Meta, Dir, RunArgs) -> no_return()
|
||||||
@ -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).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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).
|
||||||
|
|||||||
@ -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]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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} ->
|
||||||
Realm = cx_load_realm_meta(Meta),
|
Record = cx_load_realm_meta(Meta),
|
||||||
[Realm | Realms];
|
[{Realm, Record} | CX];
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
Message = "Loading realm file ~tp failed with: ~tp. Skipping...",
|
Message = "Loading realm ~tp failed with: ~tp. Skipping...",
|
||||||
ok = log(warning, Message, [File, Reason]),
|
ok = log(warning, Message, [Realm, Reason]),
|
||||||
Realms
|
CX
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
@ -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,31 +76,19 @@ 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.
|
||||||
|
|
||||||
@ -108,17 +96,27 @@ prompt_keygen() ->
|
|||||||
-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,6 +177,8 @@ gen_p_key(KeyFile) ->
|
|||||||
%% way.
|
%% way.
|
||||||
|
|
||||||
der_to_pem(KeyFile, PemFile) ->
|
der_to_pem(KeyFile, PemFile) ->
|
||||||
|
case openssl() of
|
||||||
|
{ok, OpenSSL} ->
|
||||||
Command =
|
Command =
|
||||||
io_lib:format("~ts rsa"
|
io_lib:format("~ts rsa"
|
||||||
" -inform DER"
|
" -inform DER"
|
||||||
@ -186,9 +186,12 @@ der_to_pem(KeyFile, PemFile) ->
|
|||||||
" -outform PEM"
|
" -outform PEM"
|
||||||
" -pubout"
|
" -pubout"
|
||||||
" -out ~ts",
|
" -out ~ts",
|
||||||
[openssl(), KeyFile, PemFile]),
|
[OpenSSL, KeyFile, PemFile]),
|
||||||
Out = os:cmd(Command),
|
Out = os:cmd(Command),
|
||||||
io:format(Out).
|
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).
|
|
||||||
|
|||||||
@ -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).
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user