Merge pull request #3 from zxq9/dev

20 nights in the ice is a long time when there's hostiles on the hill.
This commit is contained in:
Craig Everett 2018-05-31 19:59:46 +09:00 committed by GitHub
commit b60557e962
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 364 additions and 411 deletions

5
zomp/etc/otpr/zxq9.user Normal file
View File

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

View File

@ -31,4 +31,6 @@ log(Level, Format, Args) ->
warning -> "[WARNING]"; warning -> "[WARNING]";
error -> "[ERROR]" error -> "[ERROR]"
end, end,
io:format("~s ~p: " ++ Format ++ "~n", [Tag, self() | Args]). Out = io_lib:format("~s ~p: " ++ Format ++ "~n", [Tag, self() | Args]),
UTF8 = unicode:characters_to_binary(Out),
io:format(UTF8).

View File

@ -30,7 +30,8 @@
key_id/0, key_name/0, key_data/0, key_id/0, key_name/0, key_data/0,
user_id/0, user_name/0, contact_info/0, user_data/0, user_id/0, user_name/0, contact_info/0, user_data/0,
lower0_9/0, label/0, lower0_9/0, label/0,
package_meta/0]). package_meta/0,
outcome/0]).
-include("zx_logger.hrl"). -include("zx_logger.hrl").
@ -65,6 +66,11 @@
deps := [package_id()], deps := [package_id()],
type := app | lib}. type := app | lib}.
-type outcome() :: ok
| {error, Reason :: atom()}
| {error, Code :: non_neg_integer()}
| {error, Info :: string(), Code :: non_neg_integer()}.
%%% Command Dispatch %%% Command Dispatch
@ -89,114 +95,103 @@ do(["runlocal" | ArgV]) ->
run_local(ArgV); run_local(ArgV);
do(["init", "app", PackageString]) -> do(["init", "app", PackageString]) ->
ok = compatibility_check([unix]), ok = compatibility_check([unix]),
ok = zx_local:initialize(app, PackageString), done(zx_local:initialize(app, PackageString));
halt(0);
do(["init", "lib", PackageString]) -> do(["init", "lib", PackageString]) ->
ok = compatibility_check([unix]), ok = compatibility_check([unix]),
ok = zx_local:initialize(lib, PackageString), done(zx_local:initialize(lib, PackageString));
halt(0);
do(["install", PackageFile]) -> do(["install", PackageFile]) ->
ok = zx_local:assimilate(PackageFile), done(zx_local:assimilate(PackageFile));
halt(0);
do(["set", "dep", PackageString]) -> do(["set", "dep", PackageString]) ->
ok = zx_local:set_dep(PackageString), done(zx_local:set_dep(PackageString));
halt(0);
do(["set", "version", VersionString]) -> do(["set", "version", VersionString]) ->
ok = compatibility_check([unix]), ok = compatibility_check([unix]),
ok = zx_local:set_version(VersionString), done(zx_local:set_version(VersionString));
halt(0);
do(["verup", Level]) -> do(["verup", Level]) ->
ok = compatibility_check([unix]), ok = compatibility_check([unix]),
ok = zx_local:verup(Level), done(zx_local:verup(Level));
halt(0);
do(["list", "realms"]) -> do(["list", "realms"]) ->
ok = zx_local:list_realms(), done(zx_local:list_realms());
halt(0);
do(["list", "packages", Realm]) -> do(["list", "packages", Realm]) ->
ok = start(), ok = start(),
ok = zx_local:list_packages(Realm), done(zx_local:list_packages(Realm));
halt(0);
do(["list", "versions", PackageName]) -> do(["list", "versions", PackageName]) ->
ok = start(), ok = start(),
ok = zx_local:list_versions(PackageName), done(zx_local:list_versions(PackageName));
halt(0);
do(["add", "realm", RealmFile]) -> do(["add", "realm", RealmFile]) ->
ok = zx_local:add_realm(RealmFile), done(zx_local:add_realm(RealmFile));
halt(0);
do(["drop", "dep", PackageString]) -> do(["drop", "dep", PackageString]) ->
PackageID = zx_lib:package_id(PackageString), PackageID = zx_lib:package_id(PackageString),
ok = zx_local:drop_dep(PackageID), done(zx_local:drop_dep(PackageID));
halt(0);
do(["package"]) -> do(["package"]) ->
{ok, TargetDir} = file:get_cwd(), {ok, TargetDir} = file:get_cwd(),
zx_local:package(TargetDir); zx_local:package(TargetDir);
do(["package", TargetDir]) -> do(["package", TargetDir]) ->
case filelib:is_dir(TargetDir) of case filelib:is_dir(TargetDir) of
true -> true -> done(zx_local:package(TargetDir));
ok = zx_local:package(TargetDir), false -> done({error, "Target directory does not exist", 22})
halt(0);
false ->
ok = log(error, "Target directory ~tp does not exist!", [TargetDir]),
halt(22)
end; end;
do(["dialyze"]) -> do(["dialyze"]) ->
ok = zx_local:dialyze(), done(zx_local:dialyze());
halt(0);
do(["create", "user", Realm, Name]) -> do(["create", "user", Realm, Name]) ->
ok = zx_local:create_user(Realm, Name), done(zx_local:create_user(Realm, Name));
halt(0);
do(["create", "keypair"]) -> do(["create", "keypair"]) ->
ok = zx_local:grow_a_pair(), done(zx_local:grow_a_pair());
halt(0);
do(["drop", "key", Realm, KeyName]) -> do(["drop", "key", Realm, KeyName]) ->
ok = zx_local:drop_key({Realm, KeyName}), done(zx_local:drop_key({Realm, KeyName}));
halt(0);
do(["create", "plt"]) -> do(["create", "plt"]) ->
ok = zx_local:create_plt(), done(zx_local:create_plt());
halt(0);
do(["create", "realm"]) -> do(["create", "realm"]) ->
ok = zx_local:create_realm(), done(zx_local:create_realm());
halt(0);
do(["create", "realmfile", Realm]) -> do(["create", "realmfile", Realm]) ->
ok = zx_local:create_realmfile(Realm, "."), done(zx_local:create_realmfile(Realm, "."));
halt(0);
do(["takeover", Realm]) -> do(["takeover", Realm]) ->
ok = zx_local:takeover(Realm), done(zx_local:takeover(Realm));
halt(0);
do(["abdicate", Realm]) -> do(["abdicate", Realm]) ->
ok = zx_local:abdicate(Realm), done(zx_local:abdicate(Realm));
halt(0);
do(["drop", "realm", Realm]) -> do(["drop", "realm", Realm]) ->
ok = zx_local:drop_realm(Realm), done(zx_local:drop_realm(Realm));
halt(0);
do(["list", "pending", PackageName]) -> do(["list", "pending", PackageName]) ->
zx_auth:list_pending(PackageName); done(zx_auth:list_pending(PackageName));
do(["list", "resigns", Realm]) -> do(["list", "resigns", Realm]) ->
zx_auth:list_resigns(Realm); done(zx_auth:list_resigns(Realm));
do(["submit", PackageFile]) -> do(["submit", PackageFile]) ->
zx_auth:submit(PackageFile); done(zx_auth:submit(PackageFile));
do(["review", PackageString]) -> do(["review", PackageString]) ->
zx_auth:review(PackageString); done(zx_auth:review(PackageString));
do(["approve", PackageString]) -> do(["approve", PackageString]) ->
PackageID = zx_lib:package_id(PackageString), PackageID = zx_lib:package_id(PackageString),
zx_auth:approve(PackageID); done(zx_auth:approve(PackageID));
do(["reject", PackageString]) -> do(["reject", PackageString]) ->
PackageID = zx_lib:package_id(PackageString), PackageID = zx_lib:package_id(PackageString),
zx_auth:reject(PackageID); done(zx_auth:reject(PackageID));
do(["accept", PackageString]) -> do(["accept", PackageString]) ->
zx_auth:accept(PackageString); done(zx_auth:accept(PackageString));
do(["add", "packager", Package, UserName]) -> do(["add", "packager", Package, UserName]) ->
zx_auth:add_packager(Package, UserName); done(zx_auth:add_packager(Package, UserName));
do(["add", "maintainer", Package, UserName]) -> do(["add", "maintainer", Package, UserName]) ->
zx_auth:add_maintainer(Package, UserName); done(zx_auth:add_maintainer(Package, UserName));
do(["add", "sysop", Package, UserName]) -> do(["add", "sysop", Package, UserName]) ->
zx_auth:add_sysop(Package, UserName); done(zx_auth:add_sysop(Package, UserName));
do(["add", "package", PackageName]) -> do(["add", "package", PackageName]) ->
zx_auth:add_package(PackageName); done(zx_auth:add_package(PackageName));
do(_) -> do(_) ->
usage_exit(22). usage_exit(22).
-spec done(outcome()) -> no_return().
done(ok) ->
halt(0);
done({error, Reason}) when is_atom(Reason) ->
ok = log(error, "Operation failed with: ~tp", [Reason]),
halt(1);
done({error, Code}) when is_integer(Code) ->
halt(Code);
done({error, Info, Code}) ->
ok = log(error, Info),
halt(Code).
-spec compatibility_check(Platforms) -> ok | no_return() -spec compatibility_check(Platforms) -> ok | no_return()
when Platforms :: unix | win32. when Platforms :: unix | win32.
%% @private %% @private
@ -335,7 +330,7 @@ run(Identifier, RunArgs) ->
end, end,
{ok, PackageID} = ensure_installed(FuzzyID), {ok, PackageID} = ensure_installed(FuzzyID),
ok = build(PackageID), ok = build(PackageID),
Dir = zx_lib:package_dir(PackageID), Dir = zx_lib:path(lib, PackageID),
{ok, Meta} = zx_lib:read_project_meta(Dir), {ok, Meta} = zx_lib:read_project_meta(Dir),
prepare(PackageID, Meta, Dir, RunArgs). prepare(PackageID, Meta, Dir, RunArgs).
@ -511,7 +506,7 @@ fetch_one(PackageID) ->
%% input was found, or no match was found at all. %% input was found, or no match was found at all.
resolve_installed_version({Realm, Name, Version}) -> resolve_installed_version({Realm, Name, Version}) ->
PackageDir = filename:join(["lib", Realm, Name]), PackageDir = zx_lib:path(lib, Realm, Name),
case filelib:is_dir(PackageDir) of case filelib:is_dir(PackageDir) of
true -> resolve_installed_version(PackageDir, Version); true -> resolve_installed_version(PackageDir, Version);
false -> not_found false -> not_found
@ -545,10 +540,10 @@ tuplize(String, Acc) ->
%% - The package is not already installed %% - The package is not already installed
%% - If this function crashes it will completely halt the system %% - If this function crashes it will completely halt the system
install(PackageID) -> install(PackageID = {Realm, Name, _}) ->
{ok, PackageString} = zx_lib:package_string(PackageID), {ok, PackageString} = zx_lib:package_string(PackageID),
ok = log(info, "Installing ~ts", [PackageString]), ok = log(info, "Installing ~ts", [PackageString]),
ZrpFile = filename:join("zsp", zx_lib:namify_zsp(PackageID)), ZrpFile = filename:join(zx_lib:path(zsp, Realm, Name), zx_lib:namify_zsp(PackageID)),
Files = zx_lib:extract_zsp_or_die(ZrpFile), Files = zx_lib:extract_zsp_or_die(ZrpFile),
TgzFile = zx_lib:namify_tgz(PackageID), TgzFile = zx_lib:namify_tgz(PackageID),
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
@ -557,7 +552,7 @@ install(PackageID) ->
{KeyID, Signature} = maps:get(sig, Meta), {KeyID, Signature} = maps:get(sig, Meta),
{ok, PubKey} = zx_key:load(public, KeyID), {ok, PubKey} = zx_key:load(public, KeyID),
ok = ensure_package_dirs(PackageID), ok = ensure_package_dirs(PackageID),
PackageDir = filename:join("lib", PackageString), PackageDir = zx_lib:path(lib, PackageID),
ok = zx_lib:force_dir(PackageDir), ok = zx_lib:force_dir(PackageDir),
ok = zx_key:verify(TgzData, Signature, PubKey), ok = zx_key:verify(TgzData, Signature, PubKey),
ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageDir}]), ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageDir}]),
@ -570,34 +565,24 @@ install(PackageID) ->
build(PackageID) -> build(PackageID) ->
{ok, CWD} = file:get_cwd(), {ok, CWD} = file:get_cwd(),
ok = file:set_cwd(zx_lib:package_dir(PackageID)), ok = file:set_cwd(zx_lib:path(lib, PackageID)),
ok = zx_lib:build(), ok = zx_lib:build(),
file:set_cwd(CWD). file:set_cwd(CWD).
%%% Directory & File Management
-spec ensure_package_dirs(package_id()) -> ok. -spec ensure_package_dirs(package_id()) -> ok.
%% @private %% @private
%% Procedure to guarantee that directory locations necessary for the indicated app to %% Procedure to guarantee that directory locations necessary for the indicated app to
%% run have been created or halt execution. %% run have been created or halt execution.
ensure_package_dirs(PackageID = {Realm, Name, _}) -> ensure_package_dirs(PackageID) ->
Package = {Realm, Name}, Dirs = [zx_lib:path(D, PackageID) || D <- [etc, var, tmp, log, lib]],
PackageHome = zx_lib:package_dir(PackageID), lists:foreach(fun zx_lib:force_dir/1, Dirs).
PackageData = zx_lib:package_dir("var", Package),
PackageConf = zx_lib:package_dir("etc", Package),
Dirs = [PackageHome, PackageData, PackageConf],
ok = lists:foreach(fun zx_lib:force_dir/1, Dirs),
log(info, "Created dirs:~n\t~ts~n\t~ts~n\t~ts", Dirs).
%%% Usage %%% Usage
-spec usage_exit(Code) -> no_return() -spec usage_exit(Code) -> no_return()
when Code :: integer(). when Code :: integer().
%% @private %% @private

View File

@ -396,7 +396,7 @@ request_zsp(Socket, PackageID) ->
receive_zsp(Socket, PackageID) -> receive_zsp(Socket, PackageID) ->
receive receive
{tcp, Socket, Bin} -> {tcp, Socket, Bin} ->
ZrpPath = filename:join("zsp", zx_lib:namify_zsp(PackageID)), ZrpPath = zx_lib:zsp_path(PackageID),
ok = file:write_file(ZrpPath, Bin), ok = file:write_file(ZrpPath, Bin),
ok = zx_net:send(Socket, ok), ok = zx_net:send(Socket, ok),
log(info, "Wrote ~ts", [ZrpPath]); log(info, "Wrote ~ts", [ZrpPath]);
@ -520,7 +520,7 @@ terminate() ->
%% sourced, but exit with an error if it cannot locate or acquire the package. %% sourced, but exit with an error if it cannot locate or acquire the package.
% %
%ensure_dep(Socket, PackageID) -> %ensure_dep(Socket, PackageID) ->
% ZrpFile = filename:join("zsp", namify_zsp(PackageID)), % ZrpFile = zx_lib:zsp_path(PackageID),
% ok = % ok =
% case filelib:is_regular(ZrpFile) of % case filelib:is_regular(ZrpFile) of
% true -> ok; % true -> ok;

View File

@ -172,7 +172,7 @@
{meta = none :: none | zx:package_meta(), {meta = none :: none | zx:package_meta(),
home = none :: none | file:filename(), home = none :: none | file:filename(),
argv = none :: none | [string()], argv = none :: none | [string()],
sys_conf = zx_conf_sys:load() :: zx_conf_sys:data(), sys_conf = zx_sys_conf:load() :: zx_sys_conf:data(),
id = 0 :: id(), id = 0 :: id(),
actions = [] :: [request()], actions = [] :: [request()],
requests = maps:new() :: requests(), requests = maps:new() :: requests(),
@ -1366,8 +1366,7 @@ 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() ->
Home = zx_lib:zomp_dir(), Pattern = filename:join([zx_lib:path(etc), "*", "realm.conf"]),
Pattern = filename:join(Home, "*.realm"),
case filelib:wildcard(Pattern) of case filelib:wildcard(Pattern) of
[] -> {error, no_realms}; [] -> {error, no_realms};
RealmFiles -> {ok, cx_populate(RealmFiles, [])} RealmFiles -> {ok, cx_populate(RealmFiles, [])}
@ -1481,7 +1480,7 @@ cx_write_cache({Realm,
-spec cx_cache_file(zx:realm()) -> file:filename(). -spec cx_cache_file(zx:realm()) -> file:filename().
cx_cache_file(Realm) -> cx_cache_file(Realm) ->
filename:join(zx_lib:zomp_dir(), Realm ++ ".cache"). filename:join(zx_lib:path(var, Realm), "realm.cache").
-spec cx_realms(conn_index()) -> [zx:realms()]. -spec cx_realms(conn_index()) -> [zx:realms()].

View File

@ -12,7 +12,7 @@
-copyright("Craig Everett <zxq9@zxq9.com>"). -copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0"). -license("GPL-3.0").
-export([ensure_keypair/1, have_public_key/1, have_private_key/1, -export([ensure_keypair/1, have_key/2, keypath/2,
prompt_keygen/0, generate_rsa/1, prompt_keygen/0, generate_rsa/1,
load/2, verify/3]). load/2, verify/3]).
@ -26,42 +26,45 @@
%% Check if both the public and private key based on KeyID exists. %% Check if both the public and private key based on KeyID exists.
ensure_keypair(KeyID = {Realm, KeyName}) -> ensure_keypair(KeyID = {Realm, KeyName}) ->
case {have_public_key(KeyID), have_private_key(KeyID)} of case {have_key(public, KeyID), have_key(private, KeyID)} of
{true, true} -> {true, true} ->
true; true;
{false, true} -> {false, true} ->
Message = "Public key for ~tp/~tp cannot be found", Message = "Public key ~tp/~tp cannot be found",
ok = log(error, Message, [Realm, KeyName]), ok = log(error, Message, [Realm, KeyName]),
halt(1); halt(1);
{true, false} -> {true, false} ->
Message = "Private key for ~tp/~tp cannot be found", Message = "Private key ~tp/~tp cannot be found",
ok = log(error, Message, [Realm, KeyName]), ok = log(error, Message, [Realm, KeyName]),
halt(1); halt(1);
{false, false} -> {false, false} ->
Message = "Key pair for ~tp/~tp cannot be found", Message = "Key pair ~tp/~tp cannot be found",
ok = log(error, Message, [Realm, KeyName]), ok = log(error, Message, [Realm, KeyName]),
halt(1) halt(1)
end. end.
-spec have_public_key(zx:key_id()) -> boolean(). -spec have_key(Type, KeyID) -> boolean()
when Type :: public | private,
KeyID :: zx:key_id().
%% @private %% @private
%% Determine whether the public key indicated by KeyID is in the keystore. %% Determine whether the indicated key is present.
have_public_key({Realm, KeyName}) -> have_key(Type, KeyID) ->
PublicKeyFile = KeyName ++ ".pub.der", filelib:is_regular(keypath(Type, KeyID)).
PublicKeyPath = filename:join([zx_lib:zomp_dir(), "key", Realm, PublicKeyFile]),
filelib:is_regular(PublicKeyPath).
-spec have_private_key(zx:key_id()) -> boolean(). -spec keypath(Type, KeyID) -> Path
when Type :: public | private,
KeyID :: zx:key_id(),
Path :: file:filename().
%% @private %% @private
%% Determine whether the private key indicated by KeyID is in the keystore. %% Given KeyID, return the path to the key type indicated.
have_private_key({Realm, KeyName}) -> keypath(public, {Realm, KeyName}) ->
PrivateKeyFile = KeyName ++ ".key.der", filename:join(zx_lib:path(key, Realm), KeyName ++ ".pub.der");
PrivateKeyPath = filename:join([zx_lib:zomp_dir(), "key", Realm, PrivateKeyFile]), keypath(private, {Realm, KeyName}) ->
filelib:is_regular(PrivateKeyPath). filename:join(zx_lib:path(key, Realm), KeyName ++ ".key.der").
@ -111,12 +114,10 @@ prompt_keygen() ->
%% 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({Realm, KeyName}) -> generate_rsa(KeyID = {Realm, KeyName}) ->
KeyDir = filename:join([zx_lib:zomp_dir(), "key", Realm]), PemFile = filename:join(zx_lib:path(key, Realm), KeyName ++ ".pub.pem"),
ok = zx_lib:force_dir(KeyDir), KeyFile = keypath(private, KeyID),
PemFile = filename:join(KeyDir, KeyName ++ ".pub.pem"), PubFile = keypath(public, KeyID),
KeyFile = filename:join(KeyDir, KeyName ++ ".key.der"),
PubFile = filename:join(KeyDir, KeyName ++ ".pub.der"),
ok = lists:foreach(fun zx_lib:halt_if_exists/1, [PemFile, KeyFile, PubFile]), 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]),
ok = gen_p_key(KeyFile), ok = gen_p_key(KeyFile),
@ -223,22 +224,17 @@ openssl() ->
when Type :: private | public, when Type :: private | public,
KeyID :: zx:key_id(), KeyID :: zx:key_id(),
Result :: {ok, DecodedKey :: term()} Result :: {ok, DecodedKey :: term()}
| {error, Reason :: term()}. | {error, Reason :: file:posix()}.
%% @private %% @private
%% Hide the details behind reading and loading DER encoded RSA key files. %% Hide the details behind reading and loading DER encoded RSA key files.
load(Type, {Realm, KeyName}) -> load(Type, KeyID) ->
{DerType, Path} = DerType =
case Type of case Type of
private -> private -> 'RSAPrivateKey';
KeyDer = KeyName ++ ".key.der", public -> 'RSAPublicKey'
P = filename:join([zx_lib:zomp_dir(), "key", Realm, KeyDer]),
{'RSAPrivateKey', P};
public ->
PubDer = KeyName ++ ".pub.der",
P = filename:join([zx_lib:zomp_dir(), "key", Realm, PubDer]),
{'RSAPublicKey', P}
end, end,
Path = keypath(Type, KeyID),
ok = log(info, "Loading key from file ~ts", [Path]), ok = log(info, "Loading key from file ~ts", [Path]),
case file:read_file(Path) of case file:read_file(Path) of
{ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)}; {ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)};

View File

@ -18,20 +18,21 @@
path/1, path/2, path/3, path/4, ppath/2, path/1, path/2, path/3, path/4, ppath/2,
force_dir/1, mktemp_dir/1, force_dir/1, mktemp_dir/1,
list_realms/0, list_realms/0,
hosts_cache_file/1, get_prime/1, realm_meta/1, get_prime/1, realm_meta/1,
read_project_meta/0, read_project_meta/1, read_package_meta/1, read_project_meta/0, read_project_meta/1, read_package_meta/1,
write_project_meta/1, write_project_meta/2, write_project_meta/1, write_project_meta/2,
write_terms/2, write_terms/2, exec_shell/1,
valid_lower0_9/1, valid_label/1, valid_version/1, valid_lower0_9/1, valid_label/1, valid_version/1,
string_to_version/1, version_to_string/1, string_to_version/1, version_to_string/1,
package_id/1, package_string/1, package_id/1, package_string/1,
package_dir/1, package_dir/2,
namify_zsp/1, namify_tgz/1, namify_zsp/1, namify_tgz/1,
zsp_path/1,
find_latest_compatible/2, installed/1, find_latest_compatible/2, installed/1,
realm_conf/1, load_realm_conf/1, realm_conf/1, load_realm_conf/1,
extract_zsp_or_die/1, halt_if_exists/1, extract_zsp_or_die/1, halt_if_exists/1,
build/0, build/0,
rm_rf/1, rm/1]). rm_rf/1, rm/1,
b_to_t/1, b_to_ts/1]).
-include("zx_logger.hrl"). -include("zx_logger.hrl").
@ -184,14 +185,6 @@ mktemp_dir({Realm, Name}) ->
end. end.
-spec hosts_cache_file(zx:realm()) -> file:filename().
%% @private
%% Given a Realm name, construct a realm's .hosts filename and return it.
hosts_cache_file(Realm) ->
filename:join(zomp_dir(), Realm ++ ".hosts").
-spec list_realms() -> [zx:realm()]. -spec list_realms() -> [zx:realm()].
%% @private %% @private
%% Check the filesystem for etc/[Realm Name]/realm.conf files. %% Check the filesystem for etc/[Realm Name]/realm.conf files.
@ -247,7 +240,8 @@ read_project_meta() ->
-spec read_project_meta(Dir) -> Result -spec read_project_meta(Dir) -> Result
when Dir :: file:filename(), when Dir :: file:filename(),
Result :: {ok, zx:package_meta()} Result :: {ok, zx:package_meta()}
| {error, file:posix()}. | {error, file:posix()}
| {error, file:posix(), non_neg_integer()}.
%% @private %% @private
%% Read the `zomp.meta' file from the indicated directory, if possible. %% Read the `zomp.meta' file from the indicated directory, if possible.
@ -256,9 +250,10 @@ read_project_meta(Dir) ->
case file:consult(Path) of case file:consult(Path) of
{ok, Meta} -> {ok, Meta} ->
{ok, maps:from_list(Meta)}; {ok, maps:from_list(Meta)};
{error, enoent} ->
{error, "No project zomp.meta file. Wrong directory? Not initialized?", 2};
Error -> Error ->
ok = log(error, "Failed to open \"zomp.meta\" with ~tp", [Error]), ok = log(error, "Read from zomp.meta failed with: ~tp", [Error]),
ok = log(error, "Wrong directory?"),
Error Error
end. end.
@ -268,10 +263,8 @@ read_project_meta(Dir) ->
Result :: {ok, zx:package_meta()} Result :: {ok, zx:package_meta()}
| {error, file:posix()}. | {error, file:posix()}.
read_package_meta({Realm, Name, Version}) -> read_package_meta(PackageID) ->
{ok, VersionString} = version_to_string(Version), read_project_meta(path(lib, PackageID)).
Path = filename:join([zomp_dir(), "lib", Realm, Name, VersionString]),
read_project_meta(Path).
-spec write_project_meta(Meta) -> Result -spec write_project_meta(Meta) -> Result
@ -319,6 +312,21 @@ write_terms(Filename, List) ->
file:write_file(Filename, Text). file:write_file(Filename, Text).
-spec exec_shell(CMD) -> ok
when CMD :: string().
%% @private
%% Print the output of an os:cmd/1 event only if there is any.
exec_shell(CMD) ->
case os:cmd(CMD) of
"" ->
ok;
Out ->
Trimmed = string:trim(Out, trailing, "\r\n"),
log(info, "os:cmd(~tp) -> ~ts", [CMD, Trimmed])
end.
-spec valid_lower0_9(string()) -> boolean(). -spec valid_lower0_9(string()) -> boolean().
%% @private %% @private
%% Check whether a provided string is a valid lower0_9. %% Check whether a provided string is a valid lower0_9.
@ -570,31 +578,6 @@ package_string(_) ->
{error, invalid_package_id}. {error, invalid_package_id}.
-spec package_dir(zx:package_id()) -> file:filename().
%% @private
%% Returns the path to a package installation. Crashes if PackageID is not a valid
%% identitifer or if the version is incomplete (it is not possible to create a path
%% to a partial version number).
package_dir({Realm, Name, Version = {X, Y, Z}})
when is_integer(X), is_integer(Y), is_integer(Z) ->
{ok, PackageDir} = package_string({Realm, Name}),
{ok, VersionDir} = version_to_string(Version),
filename:join([zomp_dir(), "lib", PackageDir, VersionDir]).
-spec package_dir(Prefix, Package) -> PackageDataDir
when Prefix :: string(),
Package :: zx:package(),
PackageDataDir :: file:filename().
%% @private
%% Create an absolute path to an application directory prefixed by the inclued argument.
package_dir(Prefix, {Realm, Name}) ->
PackageString = package_string({Realm, Name, {z, z, z}}),
filename:join([zomp_dir(), Prefix, PackageString]).
-spec namify_zsp(PackageID) -> ZrpFileName -spec namify_zsp(PackageID) -> ZrpFileName
when PackageID :: zx:package_id(), when PackageID :: zx:package_id(),
ZrpFileName :: file:filename(). ZrpFileName :: file:filename().
@ -626,6 +609,12 @@ namify(PackageID, Suffix) ->
PackageString ++ "." ++ Suffix. PackageString ++ "." ++ Suffix.
-spec zsp_path(zx:package_id()) -> file:filename().
zsp_path(PackageID) ->
filename:join(path(zsp, element(1, PackageID)), namify_zsp(PackageID)).
-spec find_latest_compatible(Version, Versions) -> Result -spec find_latest_compatible(Version, Versions) -> Result
when Version :: zx:version(), when Version :: zx:version(),
Versions :: [zx:version()], Versions :: [zx:version()],
@ -670,26 +659,24 @@ latest_compatible(Version, Versions) ->
%% True to its name, tells whether a package's install directory is found. %% True to its name, tells whether a package's install directory is found.
installed(PackageID) -> installed(PackageID) ->
filelib:is_dir(package_dir(PackageID)). filelib:is_dir(path(lib, PackageID)).
-spec realm_conf(Realm) -> Path
-spec realm_conf(Realm) -> RealmFileName
when Realm :: string(), when Realm :: string(),
RealmFileName :: file:filename(). Path :: file:filename().
%% @private %% @private
%% Take a realm name, and return the name of the realm filename that would result. %% Given a realm name return the path to its conf file.
realm_conf(Realm) -> realm_conf(Realm) ->
Realm ++ ".realm". filename:join(path(etc, Realm), "realm.conf").
-spec load_realm_conf(Realm) -> Result -spec load_realm_conf(Realm) -> Result
when Realm :: zx:realm(), when Realm :: zx:realm(),
Result :: {ok, RealmConf} Result :: {ok, RealmConf}
| {error, Reason}, | {error, Reason},
RealmConf :: list(), RealmConf :: map(),
Reason :: badarg Reason :: badarg
| terminated | terminated
| system_limit | system_limit
@ -699,13 +686,13 @@ realm_conf(Realm) ->
%% Load the config for the given realm or halt with an error. %% Load the config for the given realm or halt with an error.
load_realm_conf(Realm) -> load_realm_conf(Realm) ->
Path = filename:join(path(etc, Realm), "realm.conf"), Path = realm_conf(Realm),
case file:consult(Path) of case file:consult(Path) of
{ok, C} -> {ok, C} ->
C; {ok, maps:from_list(C)};
{error, enoent} -> Error ->
ok = log(warning, "Realm ~tp is not configured.", [Realm]), ok = log(warning, "Loading realm conf ~ts failed with: ~tp", [Path, Error]),
halt(1) Error
end. end.
@ -792,6 +779,30 @@ rm(Path) ->
end. end.
-spec b_to_t(binary()) -> {ok, term()} | error.
%% @private
%% A wrapper for the binary_to_term/1 BIF to hide the try..catch mess in the places we
%% don't want to crash on funky input.
b_to_t(Binary) ->
try
binary_to_term(Binary)
catch
error:badarg -> error
end.
-spec b_to_ts(binary()) -> {ok, term()} | error.
%% A wrapper for the binary_to_term/2 BIF to hide the try..catch mess in the places we
%% don't want to crash on funky input.
b_to_ts(Binary) ->
try
binary_to_term(Binary, [safe])
catch
error:badarg -> error
end.
%%% Error exits %%% Error exits

View File

@ -25,7 +25,7 @@
%%% Functions %%% Functions
-spec initialize(Type, PackageString) -> ok -spec initialize(Type, PackageString) -> zx:outcome()
when Type :: app | lib, when Type :: app | lib,
PackageString :: string(). PackageString :: string().
%% @private %% @private
@ -33,32 +33,35 @@
%% and interaction with the user to determine a few details. %% and interaction with the user to determine a few details.
initialize(Type, RawPackageString) -> initialize(Type, RawPackageString) ->
ok =
case filelib:is_file("zomp.meta") of case filelib:is_file("zomp.meta") of
false -> ok; false -> initialize2(Type, RawPackageString);
true -> error_exit("This project is already Zompified.", ?LINE) true -> {error, "This project is already Zompified.", 17}
end, end.
PackageID = {Realm, Name, _} =
initialize2(Type, RawPackageString) ->
case zx_lib:package_id(RawPackageString) of case zx_lib:package_id(RawPackageString) of
{ok, {R, N, {z, z, z}}} -> {ok, {R, N, {z, z, z}}} ->
{R, N, {0, 1, 0}}; initialize3(Type, {R, N, {0, 1, 0}});
{ok, {R, N, {X, z, z}}} -> {ok, {R, N, {X, z, z}}} ->
{R, N, {X, 0, 0}}; initialize3(Type, {R, N, {X, 0, 0}});
{ok, {R, N, {X, Y, z}}} -> {ok, {R, N, {X, Y, z}}} ->
{R, N, {X, Y, 0}}; initialize3(Type, {R, N, {X, Y, 0}});
{ok, ID} -> {ok, ID} ->
ID; initialize3(Type, ID);
{error, invalid_package_string} -> {error, invalid_package_string} ->
error_exit("Invalid package string: ~tp", [RawPackageString], ?LINE) {error, "Invalid package string.", 22}
end, end.
initialize3(Type, PackageID) ->
case package_exists(PackageID) of case package_exists(PackageID) of
false -> false ->
Prefix = solicit_prefix(), Prefix = solicit_prefix(),
initialize(Type, PackageID, Prefix); initialize(Type, PackageID, Prefix);
true -> true ->
PackageName = zx_lib:package_string({Realm, Name, {z, z, z}}), Message = "Package already exists. Try another.",
Message = "Package ~tp already exists. Try another.", {error, Message, 17}
error_exit(Message, [PackageName], ?LINE)
end. end.
@ -161,6 +164,7 @@ solicit_prefix() ->
end end
end. end.
-spec update_source_vsn(zx:version()) -> ok. -spec update_source_vsn(zx:version()) -> ok.
%% @private %% @private
%% Use grep to tell us which files have a `-vsn' attribute and which don't. %% Use grep to tell us which files have a `-vsn' attribute and which don't.
@ -177,8 +181,8 @@ update_source_vsn(Version) ->
SubF = "sed -i 's/-vsn(.*$/-vsn(\"~s\")./' $(grep -l '^-vsn(' src/*)", SubF = "sed -i 's/-vsn(.*$/-vsn(\"~s\")./' $(grep -l '^-vsn(' src/*)",
Add = lists:flatten(io_lib:format(AddF, [VersionString])), Add = lists:flatten(io_lib:format(AddF, [VersionString])),
Sub = lists:flatten(io_lib:format(SubF, [VersionString])), Sub = lists:flatten(io_lib:format(SubF, [VersionString])),
ok = exec_shell(Add), _ = os:cmd(Add),
ok = exec_shell(Sub), _ = os:cmd(Sub),
log(info, "Source version attributes set"). log(info, "Source version attributes set").
@ -240,22 +244,7 @@ initialize_app_file({_, Name, Version}, AppStart) ->
zx_lib:write_terms(AppFile, [AppProfile]). zx_lib:write_terms(AppFile, [AppProfile]).
-spec exec_shell(CMD) -> ok -spec assimilate(PackageFile) -> zx:outcome()
when CMD :: string().
%% @private
%% Print the output of an os:cmd/1 event only if there is any.
exec_shell(CMD) ->
case os:cmd(CMD) of
"" ->
ok;
Out ->
Trimmed = string:trim(Out, trailing, "\r\n"),
log(info, "os:cmd(~tp) ->~n~ts", [CMD, Trimmed])
end.
-spec assimilate(PackageFile) -> ok
when PackageFile :: file:filename(). when PackageFile :: file:filename().
%% @private %% @private
%% Receives a path to a file containing package data, examines it, and copies it to a %% Receives a path to a file containing package data, examines it, and copies it to a
@ -272,21 +261,23 @@ assimilate(PackageFile) ->
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
{KeyID, Signature} = maps:get(sig, Meta), {KeyID, Signature} = maps:get(sig, Meta),
{ok, PubKey} = zx_key:load(public, KeyID), {ok, PubKey} = zx_key:load(public, KeyID),
ok =
case public_key:verify(TgzData, sha512, Signature, PubKey) of case public_key:verify(TgzData, sha512, Signature, PubKey) of
true -> true ->
ZrpPath = filename:join("zsp", zx_lib:namify_zsp(PackageID)), ok = file:copy(PackageFile, zx_lib:zsp_path(PackageID)),
file:copy(PackageFile, ZrpPath); assimilate2(CWD, PackageID);
false -> false ->
error_exit("Bad package signature: ~ts", [PackageFile], ?LINE) {error, "Bad package signature.", 1}
end, end.
assimilate2(CWD, PackageID) ->
ok = file:set_cwd(CWD), ok = file:set_cwd(CWD),
Message = "~ts is now locally available.", Message = "~ts is now locally available.",
{ok, PackageString} = zx_lib:package_string(PackageID), {ok, PackageString} = zx_lib:package_string(PackageID),
log(info, Message, [PackageString]). log(info, Message, [PackageString]).
-spec set_dep(Identifier :: string()) -> ok. -spec set_dep(Identifier :: string()) -> zx:outcome().
%% @private %% @private
%% Set a specific dependency in the current project. If the project currently has a %% Set a specific dependency in the current project. If the project currently has a
%% dependency on the same package then the version of that dependency is updated to %% dependency on the same package then the version of that dependency is updated to
@ -296,14 +287,12 @@ assimilate(PackageFile) ->
set_dep(Identifier) -> set_dep(Identifier) ->
{ok, {Realm, Name, FuzzyVersion}} = zx_lib:package_id(Identifier), {ok, {Realm, Name, FuzzyVersion}} = zx_lib:package_id(Identifier),
Version =
case FuzzyVersion of case FuzzyVersion of
{X, Y, Z} when is_integer(X), is_integer(Y), is_integer(Z) -> {X, Y, Z} when is_integer(X), is_integer(Y), is_integer(Z) ->
{X, Y, Z}; set_dep({Realm, Name}, {X, Y, Z});
_ -> _ ->
error_exit("Incomplete version tuple: ~tp", [FuzzyVersion]) {error, "Incompelte version tuple.", 22}
end, end.
set_dep({Realm, Name}, Version).
-spec set_dep(zx:package(), zx:version()) -> ok. -spec set_dep(zx:package(), zx:version()) -> ok.
@ -313,12 +302,8 @@ set_dep({Realm, Name}, Version) ->
{ok, Meta} = zx_lib:read_project_meta(), {ok, Meta} = zx_lib:read_project_meta(),
Deps = maps:get(deps, Meta), Deps = maps:get(deps, Meta),
case lists:member(PackageID, Deps) of case lists:member(PackageID, Deps) of
true -> true -> ok;
{ok, PackageString} = zx_lib:package_string(PackageID), false -> set_dep(PackageID, Deps, Meta)
ok = log(info, "~ts is already a dependency", [PackageString]),
halt(0);
false ->
set_dep(PackageID, Deps, Meta)
end. end.
@ -351,27 +336,25 @@ set_dep(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
zx_lib:write_project_meta(NewMeta). zx_lib:write_project_meta(NewMeta).
-spec set_version(VersionString) -> ok -spec set_version(VersionString) -> zx:outcome()
when VersionString :: string(). when VersionString :: string().
%% @private %% @private
%% Convert a version string to a new version, sanitizing it in the process and returning %% Convert a version string to a new version, sanitizing it in the process and returning
%% a reasonable error message on bad input. %% a reasonable error message on bad input.
set_version(VersionString) -> set_version(VersionString) ->
NewVersion =
case zx_lib:string_to_version(VersionString) of case zx_lib:string_to_version(VersionString) of
{ok, {_, _, z}} ->
Message = "'set version' arguments must be complete, ex: 1.2.3",
error_exit(Message, ?LINE);
{ok, Version} ->
Version;
{error, invalid_version_string} -> {error, invalid_version_string} ->
Message = "Invalid version string: ~tp", Message = "Invalid version string: ~tp",
error_exit(Message, [VersionString], ?LINE) {error, Message, 22};
end, {ok, {_, _, z}} ->
Message = "'set version' arguments must be complete, ex: 1.2.3",
{error, Message, 22};
{ok, NewVersion} ->
{ok, Meta} = zx_lib:read_project_meta(), {ok, Meta} = zx_lib:read_project_meta(),
{Realm, Name, OldVersion} = maps:get(package_id, Meta), {Realm, Name, OldVersion} = maps:get(package_id, Meta),
update_version(Realm, Name, OldVersion, NewVersion, Meta). update_version(Realm, Name, OldVersion, NewVersion, Meta)
end.
-spec update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> ok -spec update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> ok
@ -390,25 +373,31 @@ set_version(VersionString) ->
update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> update_version(Realm, Name, OldVersion, NewVersion, OldMeta) ->
PackageID = {Realm, Name, NewVersion}, PackageID = {Realm, Name, NewVersion},
NewMeta = maps:put(package_id, PackageID, OldMeta), NewMeta = maps:put(package_id, PackageID, OldMeta),
ok = zx_lib:write_project_meta(NewMeta), case zx_lib:write_project_meta(NewMeta) of
ok ->
ok = update_source_vsn(NewVersion), ok = update_source_vsn(NewVersion),
ok = update_app_vsn(Name, NewVersion), ok = update_app_vsn(Name, NewVersion),
{ok, OldVS} = zx_lib:version_to_string(OldVersion), {ok, OldVS} = zx_lib:version_to_string(OldVersion),
{ok, NewVS} = zx_lib:version_to_string(NewVersion), {ok, NewVS} = zx_lib:version_to_string(NewVersion),
log(info, "Version changed from ~s to ~s.", [OldVS, NewVS]). log(info, "Version changed from ~s to ~s.", [OldVS, NewVS]);
Error ->
ok = log(error, "Write to zomp.meta failed with: ~tp", [Error]),
Error
end.
-spec list_realms() -> ok. -spec list_realms() -> ok.
%% @private %% @private
%% List all currently configured realms. The definition of a "configured realm" is a %% List all currently configured realms.
%% realm for which a .realm file exists in $ZOMP_HOME. The realms will be printed to
%% stdout and the program will exit.
list_realms() -> list_realms() ->
lists:foreach(fun(R) -> io:format("~ts~n", [R]) end, zx_lib:list_realms()). case zx_lib:list_realms() of
[] -> io:format("Oh noes! No realms are configured!~n");
Realms -> lists:foreach(fun(R) -> io:format("~ts~n", [R]) end, Realms)
end.
-spec list_packages(zx:realm()) -> ok. -spec list_packages(zx:realm()) -> zx:outcome().
%% @private %% @private
%% Contact the indicated realm and query it for a list of registered packages and print %% Contact the indicated realm and query it for a list of registered packages and print
%% them to stdout. %% them to stdout.
@ -416,62 +405,59 @@ list_realms() ->
list_packages(Realm) -> list_packages(Realm) ->
case zx_daemon:list_packages(Realm) of case zx_daemon:list_packages(Realm) of
{ok, []} -> {ok, []} ->
log(info, "Realm ~tp has no packages available.", [Realm]); io:format("Realm ~tp has no packages available.~n", [Realm]);
{ok, Packages} -> {ok, Packages} ->
Print = fun({R, N}) -> io:format("~ts-~ts~n", [R, N]) end, Print = fun({R, N}) -> io:format("~ts-~ts~n", [R, N]) end,
lists:foreach(Print, Packages); lists:foreach(Print, Packages);
{error, bad_realm} -> {error, bad_realm} ->
error_exit("Bad realm name.", ?LINE); {error, "Unconfigured realm or bad realm name.", 22};
{error, no_realm} ->
error_exit("Realm \"~ts\" is not configured.", ?LINE);
{error, network} -> {error, network} ->
Message = "Network issues are preventing connection to the realm.", Message = "Network issues are preventing connection to the realm.",
error_exit(Message, ?LINE) {error, Message, 101}
end. end.
-spec list_versions(PackageName :: string()) -> ok. -spec list_versions(PackageName :: string()) -> zx:outcome().
%% @private %% @private
%% List the available versions of the package indicated. The user enters a string-form %% List the available versions of the package indicated. The user enters a string-form
%% package name (such as "otpr-zomp") and the return values will be full package strings %% package name (such as "otpr-zomp") and the return values will be full package strings
%% of the form "otpr-zomp-1.2.3", one per line printed to stdout. %% of the form "otpr-zomp-1.2.3", one per line printed to stdout.
list_versions(PackageName) -> list_versions(PackageString) ->
Package = {Realm, Name} = case zx_lib:package_id(PackageString) of
case zx_lib:package_id(PackageName) of {ok, PackageID} ->
{ok, {R, N, {z, z, z}}} -> list_versions2(PackageID);
{R, N};
{error, invalid_package_string} -> {error, invalid_package_string} ->
error_exit("~tp is not a valid package name.", [PackageName], ?LINE) {error, "Invalid package name.", 22}
end,
case zx_daemon:list_versions(Package) of
{ok, []} ->
Message = "Package ~ts has no versions available.",
log(info, Message, [PackageName]);
{ok, Versions} ->
Print =
fun(Version) ->
{ok, PackageString} = zx_lib:package_string({Realm, Name, Version}),
io:format("~ts~n", [PackageString])
end,
lists:foreach(Print, Versions);
{error, bad_realm} ->
error_exit("Bad realm name.", ?LINE);
{error, bad_package} ->
error_exit("Bad package name.", ?LINE);
{error, network} ->
Message = "Network issues are preventing connection to the realm.",
error_exit(Message, ?LINE)
end. end.
-spec add_realm(Path) -> ok list_versions2(PackageID) ->
case zx_daemon:list_versions(PackageID) of
{ok, []} ->
io:format("No versions available.~n");
{ok, Versions} ->
Print =
fun(Version) ->
{ok, VersionString} = zx_lib:version_to_string(Version),
io:format("~ts~n", [VersionString])
end,
lists:foreach(Print, Versions);
{error, bad_realm} ->
{error, "Bad realm name.", 22};
{error, bad_package} ->
{error, "Bad package name.", 22};
{error, network} ->
Message = "Network issues are preventing connection to the realm.",
{error, Message, 101}
end.
-spec add_realm(Path) -> zx:outcome()
when Path :: file:filename(). when Path :: file:filename().
%% @private %% @private
%% Add a .realm file to $ZOMP_HOME from a location in the filesystem. %% Configure the system for a new realm by interpreting a .zrf file.
%% Print the SHA512 of the .realm file for the user so they can verify that the file %% Also log the SHA512 of the .zrf for the user.
%% is authentic. This implies, of course, that .realm maintainers are going to
%% post SHA512 sums somewhere visible.
add_realm(Path) -> add_realm(Path) ->
case file:read_file(Path) of case file:read_file(Path) of
@ -479,13 +465,30 @@ add_realm(Path) ->
Digest = crypto:hash(sha512, Data), Digest = crypto:hash(sha512, Data),
Text = integer_to_list(binary:decode_unsigned(Digest, big), 16), Text = integer_to_list(binary:decode_unsigned(Digest, big), 16),
ok = log(info, "SHA512 of ~ts: ~ts", [Path, Text]), ok = log(info, "SHA512 of ~ts: ~ts", [Path, Text]),
add_realm(Path, Data); add_realm2(Data);
{error, enoent} -> {error, enoent} ->
ok = log(warning, "FAILED: ~ts does not exist.", [Path]), {error, "Realm bundle (.zrf) does not exist.", 2};
halt(1);
{error, eisdir} -> {error, eisdir} ->
ok = log(warning, "FAILED: ~ts is a directory, not a realm file.", [Path]), {error, "Path is a directory, not a .zrf file.", 21}
halt(1) end.
-spec add_realm2(Data) -> zx:outcome()
when Data :: binary().
add_realm2(Data) ->
case zx_lib:b_to_t(Data) of
{ok, {RealmConf, KeyDER}} ->
Realm = maps:get(realm, RealmConf),
ok = make_realm_dirs(Realm),
ConfPath = zx_lib:realm_conf(Realm),
zx_lib:write_terms(ConfPath, maps:to_list(RealmConf)),
KeyName = maps:get(key, RealmConf),
KeyPath = zx_lib:keypath(public, {Realm, KeyName}),
ok = file:write_file(KeyPath, KeyDER),
log(info, "Added realm ~tp.", [Realm]);
error ->
{error, "Invalid .zrf file.", 84}
end. end.
@ -516,7 +519,7 @@ drop_dep(PackageID) ->
end. end.
-spec verup(Level) -> ok -spec verup(Level) -> zx:outcome()
when Level :: string(). when Level :: string().
%% @private %% @private
%% Convert input string arguments to acceptable atoms for use in update_version/1. %% Convert input string arguments to acceptable atoms for use in update_version/1.
@ -527,7 +530,7 @@ verup("patch") -> version_up(patch);
verup(_) -> zx:usage_exit(22). verup(_) -> zx:usage_exit(22).
-spec version_up(Level) -> ok -spec version_up(Level) -> zx:outcome()
when Level :: major when Level :: major
| minor | minor
| patch. | patch.
@ -538,13 +541,13 @@ verup(_) -> zx:usage_exit(22).
%% read for some reason. %% read for some reason.
version_up(Arg) -> version_up(Arg) ->
Meta =
case zx_lib:read_project_meta() of case zx_lib:read_project_meta() of
{ok, M} -> M; {ok, Meta} ->
Error -> error_exit("verup failed with: ~tp", [Error], ?LINE)
end,
PackageID = maps:get(package_id, Meta), PackageID = maps:get(package_id, Meta),
version_up(Arg, PackageID, Meta). version_up(Arg, PackageID, Meta);
Error ->
Error
end.
-spec version_up(Level, PackageID, Meta) -> ok -spec version_up(Level, PackageID, Meta) -> ok
@ -582,7 +585,7 @@ 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), {Realm, _, _} = maps:get(package_id, Meta),
KeyDir = filename:join([zx_lib:zomp_dir(), "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 = KeyDir ++ "/*.key.der",
case [filename:basename(F, ".key.der") || F <- filelib:wildcard(Pattern)] of case [filename:basename(F, ".key.der") || F <- filelib:wildcard(Pattern)] of
@ -712,12 +715,11 @@ dialyze() ->
true -> log(info, "Using PLT: ~tp", [PLT]); true -> log(info, "Using PLT: ~tp", [PLT]);
false -> build_plt() false -> build_plt()
end, end,
TmpDir = filename:join(zx_lib:zomp_dir(), "tmp"),
Me = escript:script_name(), Me = escript:script_name(),
EvilTwin = filename:join(TmpDir, filename:basename(Me ++ ".erl")), EvilTwin = filename:join(zx_lib:path(tmp), filename:basename(Me ++ ".erl")),
ok = log(info, "Temporarily reconstructing ~tp as ~tp", [Me, EvilTwin]), ok = log(info, "Temporarily reconstructing ~tp as ~tp", [Me, EvilTwin]),
Sed = io_lib:format("sed 's/^#!.*$//' ~s > ~s", [Me, EvilTwin]), Sed = io_lib:format("sed 's/^#!.*$//' ~s > ~s", [Me, EvilTwin]),
ok = exec_shell(Sed), ok = zx_lib:exec_shell(Sed),
ok = case dialyzer:run([{init_plt, PLT}, {from, src_code}, {files, [EvilTwin]}]) of ok = case dialyzer:run([{init_plt, PLT}, {from, src_code}, {files, [EvilTwin]}]) of
[] -> [] ->
io:format("Dialyzer found no errors and returned no warnings! Yay!~n"); io:format("Dialyzer found no errors and returned no warnings! Yay!~n");
@ -729,17 +731,14 @@ dialyze() ->
file:delete(EvilTwin). file:delete(EvilTwin).
-spec grow_a_pair() -> ok. -spec grow_a_pair() -> zx:outcome().
%% @private %% @private
%% Execute the key generation procedure for 16k RSA keys once and then terminate. %% Execute the key generation procedure for 16k RSA keys once and then terminate.
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(), KeyID = zx_key:prompt_keygen(),
case zx_key:generate_rsa(KeyID) of zx_key:generate_rsa(KeyID).
ok -> ok;
Error -> error_exit("grow_a_pair/0 error: ~tp", [Error], ?LINE)
end.
-spec drop_key(zx:key_id()) -> ok. -spec drop_key(zx:key_id()) -> ok.
@ -749,9 +748,7 @@ grow_a_pair() ->
%% error exit value (this instruction is idempotent if used in shell scripts). %% error exit value (this instruction is idempotent if used in shell scripts).
drop_key({Realm, KeyName}) -> drop_key({Realm, KeyName}) ->
ok = file:set_cwd(zx_lib:zomp_dir()), Pattern = filename:join(zx_lib:path(key, Realm), KeyName ++ ".{key,pub}.der"),
KeyGlob = KeyName ++ ".{key,pub},der",
Pattern = filename:join([zx_lib:zomp_dir(), "key", Realm, KeyGlob]),
case filelib:wildcard(Pattern) of case filelib:wildcard(Pattern) of
[] -> [] ->
log(warning, "Key ~ts/~ts not found", [Realm, KeyName]); log(warning, "Key ~ts/~ts not found", [Realm, KeyName]);
@ -780,11 +777,12 @@ create_user(Realm, Username) ->
%% realm file to the user. %% realm file to the user.
create_realm() -> create_realm() ->
ok = log(info, "WOOHOO! Making a new realm!"),
Instructions = Instructions =
"~n" "~nNAMING~n"
" Enter a name for your new realm.~n" "Enter a name for your new realm.~n"
" Names can contain only lower-case letters, numbers and the underscore.~n" "Names can contain only lower-case letters, numbers and the underscore.~n"
" Names must begin with a lower-case letter.~n", "Names must begin with a lower-case letter.~n",
ok = io:format(Instructions), ok = io:format(Instructions),
Realm = zx_tty:get_input(), Realm = zx_tty:get_input(),
case zx_lib:valid_lower0_9(Realm) of case zx_lib:valid_lower0_9(Realm) of
@ -815,11 +813,11 @@ create_realm(Realm) ->
prompt_address() -> prompt_address() ->
Message = Message =
"~n" "~nHOST ADDRESS~n"
" Enter a static, valid hostname or IPv4 or IPv6 address at which this host " "Enter a static, valid hostname or IPv4 or IPv6 address at which this host "
"can be reached from the public internet (or internal network if it will never " "can be reached from the public internet (or internal network if it will never "
"need to be reached from the internet).~n" "need to be reached from the internet).~n"
" DO NOT INCLUDE A PORT NUMBER IN THIS STEP~n", "DO NOT INCLUDE A PORT NUMBER IN THIS STEP~n",
ok = io:format(Message), ok = io:format(Message),
case zx_tty:get_input() of case zx_tty:get_input() of
"" -> "" ->
@ -845,8 +843,8 @@ parse_address(String) ->
create_realm(Realm, Address) -> create_realm(Realm, Address) ->
Message = Message =
"~n" "~nPUBLIC PORT NUMBER~n"
" Enter the public (external) port number at which this service should be " "Enter the public (external) port number at which this service should be "
"available. (This might be different from the local port number if you are " "available. (This might be different from the local port number if you are "
"forwarding ports or have a complex network layout.)~n", "forwarding ports or have a complex network layout.)~n",
ok = io:format(Message), ok = io:format(Message),
@ -860,8 +858,8 @@ create_realm(Realm, Address) ->
prompt_port_number(Current) -> prompt_port_number(Current) ->
Instructions = Instructions =
" A valid port is any number from 1 to 65535." "A valid port is any number from 1 to 65535."
" [Press enter to accept the current setting: ~tw]~n", "[Press enter to accept the current setting: ~tw]~n",
ok = io:format(Instructions, [Current]), ok = io:format(Instructions, [Current]),
case zx_tty:get_input() of case zx_tty:get_input() of
"" -> "" ->
@ -891,10 +889,10 @@ prompt_port_number(Current) ->
create_realm(Realm, Address, Port) -> create_realm(Realm, Address, Port) ->
Instructions = Instructions =
"~n" "~nSYSOP USERNAME~n"
" Enter a username for the realm sysop.~n" "Enter a username for the realm sysop.~n"
" Names can contain only lower-case letters, numbers and the underscore.~n" "Names can contain only lower-case letters, numbers and the underscore.~n"
" Names must begin with a lower-case letter.~n", "Names must begin with a lower-case letter.~n",
ok = io:format(Instructions), ok = io:format(Instructions),
UserName = zx_tty:get_input(), UserName = zx_tty:get_input(),
case zx_lib:valid_lower0_9(UserName) of case zx_lib:valid_lower0_9(UserName) of
@ -914,9 +912,9 @@ create_realm(Realm, Address, Port) ->
create_realm(Realm, Address, Port, UserName) -> create_realm(Realm, Address, Port, UserName) ->
Instructions = Instructions =
"~n" "~nSYSOP EMAIL~n"
" Enter an email address for the realm sysop.~n" "Enter an email address for the realm sysop.~n"
" Valid email address rules apply though the checking done here is quite " "Valid email address rules apply though the checking done here is quite "
"minimal. Check the address you enter carefully. The only people who will " "minimal. Check the address you enter carefully. The only people who will "
"suffer from an invalid address are your users.~n", "suffer from an invalid address are your users.~n",
ok = io:format(Instructions), ok = io:format(Instructions),
@ -949,9 +947,9 @@ create_realm(Realm, Address, Port, UserName) ->
create_realm(Realm, Address, Port, UserName, Email) -> create_realm(Realm, Address, Port, UserName, Email) ->
Instructions = Instructions =
"~n" "~nSYSOP REAL NAME~n"
" Enter the real name (or whatever name people recognize) for the sysop.~n" "Enter the real name (or whatever name people recognize) for the sysop.~n"
" There are no rules for this one. Any valid UTF-8 printables are legal.~n", "There are no rules for this one. Any valid UTF-8 printables are legal.~n",
ok = io:format(Instructions), ok = io:format(Instructions),
RealName = zx_tty:get_input(), RealName = zx_tty:get_input(),
create_realm(Realm, Address, Port, UserName, Email, RealName). create_realm(Realm, Address, Port, UserName, Email, RealName).
@ -979,9 +977,8 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
[{realm, Realm}, [{realm, Realm},
{username, UserName}, {username, UserName},
{realmname, RealName}, {realmname, RealName},
{contact_info, {"email", Email}}, {contact_info, [{"email", Email}]},
{keys, [KeyName]}], {keys, [KeyName]}],
ok = make_realm_dirs(Realm),
RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"), RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"),
ok = zx_lib:write_terms(RealmConfPath, RealmConf), ok = zx_lib:write_terms(RealmConfPath, RealmConf),
UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"), UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"),
@ -992,7 +989,7 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
"===========================================================================~n" "===========================================================================~n"
"DONE!~n" "DONE!~n"
"~n" "~n"
"The realm ~tp has been created and is accessible from the current system.~n" "The realm ~tp has been created and is accessible from this system.~n"
"~n" "~n"
"Other zomp nodes and zx users will need the new realm file, ~ts, to~n" "Other zomp nodes and zx users will need the new realm file, ~ts, to~n"
"access the realm. It does not include any public keys.~n" "access the realm. It does not include any public keys.~n"
@ -1015,9 +1012,9 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
%% Checks for remnants of a realm. %% Checks for remnants of a realm.
realm_exists(Realm) -> realm_exists(Realm) ->
Dirs = [etc, var, tmp, log, key, zsp, lib], Managed = lists:member(Realm, zx_sys_conf:managed(zx_sys_conf:load())),
Check = fun(D) -> filelib:is_file(zx_lib:path(D)) end, Dirs = [zx_lib:path(D, Realm) || D <- [etc, var, tmp, log, key, zsp, lib]],
Managed = lists:member(Realm, proplists:get_value(managed, zx_lib:read_sys_conf())), Check = fun(D) -> filelib:is_file(D) end,
Found = lists:any(Check, Dirs), Found = lists:any(Check, Dirs),
Managed or Found. Managed or Found.
@ -1028,8 +1025,8 @@ realm_exists(Realm) ->
%% or be certain some other way that the file:make_sure/1 calls will succeed. %% or be certain some other way that the file:make_sure/1 calls will succeed.
make_realm_dirs(Realm) -> make_realm_dirs(Realm) ->
Dirs = [etc, var, tmp, log, key, zsp, lib], Dirs = [zx_lib:path(D, Realm) || D <- [etc, var, tmp, log, key, zsp, lib]],
Make = fun(D) -> ok = file:make_dir(zx_lib:path(D, Realm)) end, Make = fun(D) -> ok = file:make_dir(D) end,
lists:foreach(Make, Dirs). lists:foreach(Make, Dirs).
@ -1050,10 +1047,10 @@ configure_zomp() ->
Dir :: file:filename(). Dir :: file:filename().
create_realmfile(Realm, Dir) -> create_realmfile(Realm, Dir) ->
RealmConf = zx_lib:load_realm_conf(Realm), {ok, RealmConf} = zx_lib:load_realm_conf(Realm),
ok = log(info, "Realm found, creating realm file..."), ok = log(info, "Realm found, creating realm file..."),
KeyName = proplists:get_value(key, RealmConf), KeyName = maps:get(key, RealmConf),
PubKeyPath = zx_key:name(pub, KeyName), PubKeyPath = zx_key:keypath(public, {Realm, KeyName}),
{ok, PubDER} = file:read_file(PubKeyPath), {ok, PubDER} = file:read_file(PubKeyPath),
Blob = term_to_binary({RealmConf, PubDER}), Blob = term_to_binary({RealmConf, PubDER}),
ZRF = filename:join(Dir, Realm ++ ".zrf"), ZRF = filename:join(Dir, Realm ++ ".zrf"),
@ -1061,22 +1058,6 @@ create_realmfile(Realm, Dir) ->
log(info, "Realm conf file written to ~ts", [ZRF]). log(info, "Realm conf file written to ~ts", [ZRF]).
-spec add_realm(Path, Data) -> ok
when Path :: file:filename(),
Data :: binary().
add_realm(Path, Data) ->
case erl_tar:extract({binary, Data}, [compressed, {cwd, zx_lib:zomp_dir()}]) of
ok ->
{Realm, _} = string:take(filename:basename(Path), ".", true),
log(info, "Realm ~ts is now visible to this system.", [Realm]);
{error, invalid_tar_checksum} ->
error_exit("~ts is not a valid realm file.", [Path], ?LINE);
{error, eof} ->
error_exit("~ts is not a valid realm file.", [Path], ?LINE)
end.
-spec drop_realm(zx:realm()) -> ok. -spec drop_realm(zx:realm()) -> ok.
drop_realm(Realm) -> drop_realm(Realm) ->
@ -1108,9 +1089,9 @@ drop_realm(Realm) ->
%% the realm exists, of course. %% the realm exists, of course.
takeover(Realm) -> takeover(Realm) ->
SysConf = zx_conf_sys:load(), SysConf = zx_sys_conf:load(),
case zx_conf_sys:add_managed(Realm, SysConf) of case zx_sys_conf:add_managed(Realm, SysConf) of
{ok, NewConf} -> zx_conf_sys:save(NewConf); {ok, NewConf} -> zx_sys_conf:save(NewConf);
{error, unconfigured} -> log(error, "Cannot take over an unconfigured realm.") {error, unconfigured} -> log(error, "Cannot take over an unconfigured realm.")
end. end.
@ -1118,34 +1099,8 @@ takeover(Realm) ->
-spec abdicate(zx:realm()) -> ok. -spec abdicate(zx:realm()) -> ok.
abdicate(Realm) -> abdicate(Realm) ->
SysConf = zx_conf_sys:load(), SysConf = zx_sys_conf:load(),
case zx_conf_sys:rem_managed(Realm, SysConf) of case zx_sys_conf:rem_managed(Realm, SysConf) of
{ok, NewConf} -> zx_conf_sys:save(NewConf); {ok, NewConf} -> zx_sys_conf:save(NewConf);
{error, unmanaged} -> log(error, "Cannot abdicate an unmanaged realm.") {error, unmanaged} -> log(error, "Cannot abdicate an unmanaged realm.")
end. end.
%%% 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

@ -1,5 +1,5 @@
%%% @doc %%% @doc
%%% zx_conf_sys: An interface to etc/sys.conf %%% zx_sys_conf: An interface to etc/sys.conf
%%% %%%
%%% It may seem overkill to write an interface module for a config file that only tracks %%% It may seem overkill to write an interface module for a config file that only tracks
%%% five things, but scattering this all around the project is just a bit too l33t for %%% five things, but scattering this all around the project is just a bit too l33t for
@ -13,7 +13,7 @@
%%% Bad configuration data causes a reset to defaults so that the system can function. %%% Bad configuration data causes a reset to defaults so that the system can function.
%%% @end %%% @end
-module(zx_conf_sys). -module(zx_sys_conf).
-author("Craig Everett <zxq9@zxq9.com>"). -author("Craig Everett <zxq9@zxq9.com>").
-copyright("Craig Everett <zxq9@zxq9.com>"). -copyright("Craig Everett <zxq9@zxq9.com>").
-license("GPL-3.0"). -license("GPL-3.0").