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:
commit
b60557e962
5
zomp/etc/otpr/zxq9.user
Normal file
5
zomp/etc/otpr/zxq9.user
Normal file
@ -0,0 +1,5 @@
|
||||
{realm,"otpr"}.
|
||||
{username,"zxq9"}.
|
||||
{realmname,"Craig Everett"}.
|
||||
{contact_info,{"email","zxq9@zxq9.com"}}.
|
||||
{keys,["zxq9-root"]}.
|
||||
@ -31,4 +31,6 @@ log(Level, Format, Args) ->
|
||||
warning -> "[WARNING]";
|
||||
error -> "[ERROR]"
|
||||
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).
|
||||
|
||||
@ -30,7 +30,8 @@
|
||||
key_id/0, key_name/0, key_data/0,
|
||||
user_id/0, user_name/0, contact_info/0, user_data/0,
|
||||
lower0_9/0, label/0,
|
||||
package_meta/0]).
|
||||
package_meta/0,
|
||||
outcome/0]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
|
||||
@ -65,6 +66,11 @@
|
||||
deps := [package_id()],
|
||||
type := app | lib}.
|
||||
|
||||
-type outcome() :: ok
|
||||
| {error, Reason :: atom()}
|
||||
| {error, Code :: non_neg_integer()}
|
||||
| {error, Info :: string(), Code :: non_neg_integer()}.
|
||||
|
||||
|
||||
|
||||
%%% Command Dispatch
|
||||
@ -89,114 +95,103 @@ do(["runlocal" | ArgV]) ->
|
||||
run_local(ArgV);
|
||||
do(["init", "app", PackageString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
ok = zx_local:initialize(app, PackageString),
|
||||
halt(0);
|
||||
done(zx_local:initialize(app, PackageString));
|
||||
do(["init", "lib", PackageString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
ok = zx_local:initialize(lib, PackageString),
|
||||
halt(0);
|
||||
done(zx_local:initialize(lib, PackageString));
|
||||
do(["install", PackageFile]) ->
|
||||
ok = zx_local:assimilate(PackageFile),
|
||||
halt(0);
|
||||
done(zx_local:assimilate(PackageFile));
|
||||
do(["set", "dep", PackageString]) ->
|
||||
ok = zx_local:set_dep(PackageString),
|
||||
halt(0);
|
||||
done(zx_local:set_dep(PackageString));
|
||||
do(["set", "version", VersionString]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
ok = zx_local:set_version(VersionString),
|
||||
halt(0);
|
||||
done(zx_local:set_version(VersionString));
|
||||
do(["verup", Level]) ->
|
||||
ok = compatibility_check([unix]),
|
||||
ok = zx_local:verup(Level),
|
||||
halt(0);
|
||||
done(zx_local:verup(Level));
|
||||
do(["list", "realms"]) ->
|
||||
ok = zx_local:list_realms(),
|
||||
halt(0);
|
||||
done(zx_local:list_realms());
|
||||
do(["list", "packages", Realm]) ->
|
||||
ok = start(),
|
||||
ok = zx_local:list_packages(Realm),
|
||||
halt(0);
|
||||
done(zx_local:list_packages(Realm));
|
||||
do(["list", "versions", PackageName]) ->
|
||||
ok = start(),
|
||||
ok = zx_local:list_versions(PackageName),
|
||||
halt(0);
|
||||
done(zx_local:list_versions(PackageName));
|
||||
do(["add", "realm", RealmFile]) ->
|
||||
ok = zx_local:add_realm(RealmFile),
|
||||
halt(0);
|
||||
done(zx_local:add_realm(RealmFile));
|
||||
do(["drop", "dep", PackageString]) ->
|
||||
PackageID = zx_lib:package_id(PackageString),
|
||||
ok = zx_local:drop_dep(PackageID),
|
||||
halt(0);
|
||||
done(zx_local:drop_dep(PackageID));
|
||||
do(["package"]) ->
|
||||
{ok, TargetDir} = file:get_cwd(),
|
||||
zx_local:package(TargetDir);
|
||||
do(["package", TargetDir]) ->
|
||||
case filelib:is_dir(TargetDir) of
|
||||
true ->
|
||||
ok = zx_local:package(TargetDir),
|
||||
halt(0);
|
||||
false ->
|
||||
ok = log(error, "Target directory ~tp does not exist!", [TargetDir]),
|
||||
halt(22)
|
||||
true -> done(zx_local:package(TargetDir));
|
||||
false -> done({error, "Target directory does not exist", 22})
|
||||
end;
|
||||
do(["dialyze"]) ->
|
||||
ok = zx_local:dialyze(),
|
||||
halt(0);
|
||||
done(zx_local:dialyze());
|
||||
do(["create", "user", Realm, Name]) ->
|
||||
ok = zx_local:create_user(Realm, Name),
|
||||
halt(0);
|
||||
done(zx_local:create_user(Realm, Name));
|
||||
do(["create", "keypair"]) ->
|
||||
ok = zx_local:grow_a_pair(),
|
||||
halt(0);
|
||||
done(zx_local:grow_a_pair());
|
||||
do(["drop", "key", Realm, KeyName]) ->
|
||||
ok = zx_local:drop_key({Realm, KeyName}),
|
||||
halt(0);
|
||||
done(zx_local:drop_key({Realm, KeyName}));
|
||||
do(["create", "plt"]) ->
|
||||
ok = zx_local:create_plt(),
|
||||
halt(0);
|
||||
done(zx_local:create_plt());
|
||||
do(["create", "realm"]) ->
|
||||
ok = zx_local:create_realm(),
|
||||
halt(0);
|
||||
done(zx_local:create_realm());
|
||||
do(["create", "realmfile", Realm]) ->
|
||||
ok = zx_local:create_realmfile(Realm, "."),
|
||||
halt(0);
|
||||
done(zx_local:create_realmfile(Realm, "."));
|
||||
do(["takeover", Realm]) ->
|
||||
ok = zx_local:takeover(Realm),
|
||||
halt(0);
|
||||
done(zx_local:takeover(Realm));
|
||||
do(["abdicate", Realm]) ->
|
||||
ok = zx_local:abdicate(Realm),
|
||||
halt(0);
|
||||
done(zx_local:abdicate(Realm));
|
||||
do(["drop", "realm", Realm]) ->
|
||||
ok = zx_local:drop_realm(Realm),
|
||||
halt(0);
|
||||
done(zx_local:drop_realm(Realm));
|
||||
do(["list", "pending", PackageName]) ->
|
||||
zx_auth:list_pending(PackageName);
|
||||
done(zx_auth:list_pending(PackageName));
|
||||
do(["list", "resigns", Realm]) ->
|
||||
zx_auth:list_resigns(Realm);
|
||||
done(zx_auth:list_resigns(Realm));
|
||||
do(["submit", PackageFile]) ->
|
||||
zx_auth:submit(PackageFile);
|
||||
done(zx_auth:submit(PackageFile));
|
||||
do(["review", PackageString]) ->
|
||||
zx_auth:review(PackageString);
|
||||
done(zx_auth:review(PackageString));
|
||||
do(["approve", PackageString]) ->
|
||||
PackageID = zx_lib:package_id(PackageString),
|
||||
zx_auth:approve(PackageID);
|
||||
done(zx_auth:approve(PackageID));
|
||||
do(["reject", PackageString]) ->
|
||||
PackageID = zx_lib:package_id(PackageString),
|
||||
zx_auth:reject(PackageID);
|
||||
done(zx_auth:reject(PackageID));
|
||||
do(["accept", PackageString]) ->
|
||||
zx_auth:accept(PackageString);
|
||||
done(zx_auth:accept(PackageString));
|
||||
do(["add", "packager", Package, UserName]) ->
|
||||
zx_auth:add_packager(Package, UserName);
|
||||
done(zx_auth:add_packager(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]) ->
|
||||
zx_auth:add_sysop(Package, UserName);
|
||||
done(zx_auth:add_sysop(Package, UserName));
|
||||
do(["add", "package", PackageName]) ->
|
||||
zx_auth:add_package(PackageName);
|
||||
done(zx_auth:add_package(PackageName));
|
||||
do(_) ->
|
||||
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()
|
||||
when Platforms :: unix | win32.
|
||||
%% @private
|
||||
@ -335,7 +330,7 @@ run(Identifier, RunArgs) ->
|
||||
end,
|
||||
{ok, PackageID} = ensure_installed(FuzzyID),
|
||||
ok = build(PackageID),
|
||||
Dir = zx_lib:package_dir(PackageID),
|
||||
Dir = zx_lib:path(lib, PackageID),
|
||||
{ok, Meta} = zx_lib:read_project_meta(Dir),
|
||||
prepare(PackageID, Meta, Dir, RunArgs).
|
||||
|
||||
@ -511,7 +506,7 @@ fetch_one(PackageID) ->
|
||||
%% input was found, or no match was found at all.
|
||||
|
||||
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
|
||||
true -> resolve_installed_version(PackageDir, Version);
|
||||
false -> not_found
|
||||
@ -545,10 +540,10 @@ tuplize(String, Acc) ->
|
||||
%% - The package is not already installed
|
||||
%% - If this function crashes it will completely halt the system
|
||||
|
||||
install(PackageID) ->
|
||||
install(PackageID = {Realm, Name, _}) ->
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
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),
|
||||
TgzFile = zx_lib:namify_tgz(PackageID),
|
||||
{TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files),
|
||||
@ -557,7 +552,7 @@ install(PackageID) ->
|
||||
{KeyID, Signature} = maps:get(sig, Meta),
|
||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
||||
ok = ensure_package_dirs(PackageID),
|
||||
PackageDir = filename:join("lib", PackageString),
|
||||
PackageDir = zx_lib:path(lib, PackageID),
|
||||
ok = zx_lib:force_dir(PackageDir),
|
||||
ok = zx_key:verify(TgzData, Signature, PubKey),
|
||||
ok = erl_tar:extract({binary, TgzData}, [compressed, {cwd, PackageDir}]),
|
||||
@ -570,34 +565,24 @@ install(PackageID) ->
|
||||
|
||||
build(PackageID) ->
|
||||
{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(),
|
||||
file:set_cwd(CWD).
|
||||
|
||||
|
||||
|
||||
%%% Directory & File Management
|
||||
|
||||
|
||||
-spec ensure_package_dirs(package_id()) -> ok.
|
||||
%% @private
|
||||
%% Procedure to guarantee that directory locations necessary for the indicated app to
|
||||
%% run have been created or halt execution.
|
||||
|
||||
ensure_package_dirs(PackageID = {Realm, Name, _}) ->
|
||||
Package = {Realm, Name},
|
||||
PackageHome = zx_lib:package_dir(PackageID),
|
||||
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).
|
||||
ensure_package_dirs(PackageID) ->
|
||||
Dirs = [zx_lib:path(D, PackageID) || D <- [etc, var, tmp, log, lib]],
|
||||
lists:foreach(fun zx_lib:force_dir/1, Dirs).
|
||||
|
||||
|
||||
|
||||
%%% Usage
|
||||
|
||||
|
||||
-spec usage_exit(Code) -> no_return()
|
||||
when Code :: integer().
|
||||
%% @private
|
||||
|
||||
@ -396,7 +396,7 @@ request_zsp(Socket, PackageID) ->
|
||||
receive_zsp(Socket, PackageID) ->
|
||||
receive
|
||||
{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 = zx_net:send(Socket, ok),
|
||||
log(info, "Wrote ~ts", [ZrpPath]);
|
||||
@ -520,7 +520,7 @@ terminate() ->
|
||||
%% sourced, but exit with an error if it cannot locate or acquire the package.
|
||||
%
|
||||
%ensure_dep(Socket, PackageID) ->
|
||||
% ZrpFile = filename:join("zsp", namify_zsp(PackageID)),
|
||||
% ZrpFile = zx_lib:zsp_path(PackageID),
|
||||
% ok =
|
||||
% case filelib:is_regular(ZrpFile) of
|
||||
% true -> ok;
|
||||
|
||||
@ -172,7 +172,7 @@
|
||||
{meta = none :: none | zx:package_meta(),
|
||||
home = none :: none | file:filename(),
|
||||
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(),
|
||||
actions = [] :: [request()],
|
||||
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).
|
||||
|
||||
cx_populate() ->
|
||||
Home = zx_lib:zomp_dir(),
|
||||
Pattern = filename:join(Home, "*.realm"),
|
||||
Pattern = filename:join([zx_lib:path(etc), "*", "realm.conf"]),
|
||||
case filelib:wildcard(Pattern) of
|
||||
[] -> {error, no_realms};
|
||||
RealmFiles -> {ok, cx_populate(RealmFiles, [])}
|
||||
@ -1481,7 +1480,7 @@ cx_write_cache({Realm,
|
||||
-spec cx_cache_file(zx:realm()) -> file:filename().
|
||||
|
||||
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()].
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-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,
|
||||
load/2, verify/3]).
|
||||
|
||||
@ -26,42 +26,45 @@
|
||||
%% Check if both the public and private key based on KeyID exists.
|
||||
|
||||
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;
|
||||
{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]),
|
||||
halt(1);
|
||||
{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]),
|
||||
halt(1);
|
||||
{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]),
|
||||
halt(1)
|
||||
end.
|
||||
|
||||
|
||||
-spec have_public_key(zx:key_id()) -> boolean().
|
||||
-spec have_key(Type, KeyID) -> boolean()
|
||||
when Type :: public | private,
|
||||
KeyID :: zx:key_id().
|
||||
%% @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}) ->
|
||||
PublicKeyFile = KeyName ++ ".pub.der",
|
||||
PublicKeyPath = filename:join([zx_lib:zomp_dir(), "key", Realm, PublicKeyFile]),
|
||||
filelib:is_regular(PublicKeyPath).
|
||||
have_key(Type, KeyID) ->
|
||||
filelib:is_regular(keypath(Type, KeyID)).
|
||||
|
||||
|
||||
-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
|
||||
%% 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}) ->
|
||||
PrivateKeyFile = KeyName ++ ".key.der",
|
||||
PrivateKeyPath = filename:join([zx_lib:zomp_dir(), "key", Realm, PrivateKeyFile]),
|
||||
filelib:is_regular(PrivateKeyPath).
|
||||
keypath(public, {Realm, KeyName}) ->
|
||||
filename:join(zx_lib:path(key, Realm), KeyName ++ ".pub.der");
|
||||
keypath(private, {Realm, KeyName}) ->
|
||||
filename:join(zx_lib:path(key, Realm), KeyName ++ ".key.der").
|
||||
|
||||
|
||||
|
||||
@ -111,12 +114,10 @@ prompt_keygen() ->
|
||||
%% filenames derived from Prefix.
|
||||
%% NOTE: The current version of this command is likely to only work on a unix system.
|
||||
|
||||
generate_rsa({Realm, KeyName}) ->
|
||||
KeyDir = filename:join([zx_lib:zomp_dir(), "key", Realm]),
|
||||
ok = zx_lib:force_dir(KeyDir),
|
||||
PemFile = filename:join(KeyDir, KeyName ++ ".pub.pem"),
|
||||
KeyFile = filename:join(KeyDir, KeyName ++ ".key.der"),
|
||||
PubFile = filename:join(KeyDir, KeyName ++ ".pub.der"),
|
||||
generate_rsa(KeyID = {Realm, KeyName}) ->
|
||||
PemFile = filename:join(zx_lib:path(key, Realm), KeyName ++ ".pub.pem"),
|
||||
KeyFile = keypath(private, KeyID),
|
||||
PubFile = keypath(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 = gen_p_key(KeyFile),
|
||||
@ -223,22 +224,17 @@ openssl() ->
|
||||
when Type :: private | public,
|
||||
KeyID :: zx:key_id(),
|
||||
Result :: {ok, DecodedKey :: term()}
|
||||
| {error, Reason :: term()}.
|
||||
| {error, Reason :: file:posix()}.
|
||||
%% @private
|
||||
%% Hide the details behind reading and loading DER encoded RSA key files.
|
||||
|
||||
load(Type, {Realm, KeyName}) ->
|
||||
{DerType, Path} =
|
||||
load(Type, KeyID) ->
|
||||
DerType =
|
||||
case Type of
|
||||
private ->
|
||||
KeyDer = KeyName ++ ".key.der",
|
||||
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}
|
||||
private -> 'RSAPrivateKey';
|
||||
public -> 'RSAPublicKey'
|
||||
end,
|
||||
Path = keypath(Type, KeyID),
|
||||
ok = log(info, "Loading key from file ~ts", [Path]),
|
||||
case file:read_file(Path) of
|
||||
{ok, Bin} -> {ok, public_key:der_decode(DerType, Bin)};
|
||||
|
||||
@ -18,20 +18,21 @@
|
||||
path/1, path/2, path/3, path/4, ppath/2,
|
||||
force_dir/1, mktemp_dir/1,
|
||||
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,
|
||||
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,
|
||||
string_to_version/1, version_to_string/1,
|
||||
package_id/1, package_string/1,
|
||||
package_dir/1, package_dir/2,
|
||||
namify_zsp/1, namify_tgz/1,
|
||||
zsp_path/1,
|
||||
find_latest_compatible/2, installed/1,
|
||||
realm_conf/1, load_realm_conf/1,
|
||||
extract_zsp_or_die/1, halt_if_exists/1,
|
||||
build/0,
|
||||
rm_rf/1, rm/1]).
|
||||
rm_rf/1, rm/1,
|
||||
b_to_t/1, b_to_ts/1]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
|
||||
@ -184,14 +185,6 @@ mktemp_dir({Realm, Name}) ->
|
||||
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()].
|
||||
%% @private
|
||||
%% Check the filesystem for etc/[Realm Name]/realm.conf files.
|
||||
@ -247,7 +240,8 @@ read_project_meta() ->
|
||||
-spec read_project_meta(Dir) -> Result
|
||||
when Dir :: file:filename(),
|
||||
Result :: {ok, zx:package_meta()}
|
||||
| {error, file:posix()}.
|
||||
| {error, file:posix()}
|
||||
| {error, file:posix(), non_neg_integer()}.
|
||||
%% @private
|
||||
%% Read the `zomp.meta' file from the indicated directory, if possible.
|
||||
|
||||
@ -256,9 +250,10 @@ read_project_meta(Dir) ->
|
||||
case file:consult(Path) of
|
||||
{ok, Meta} ->
|
||||
{ok, maps:from_list(Meta)};
|
||||
{error, enoent} ->
|
||||
{error, "No project zomp.meta file. Wrong directory? Not initialized?", 2};
|
||||
Error ->
|
||||
ok = log(error, "Failed to open \"zomp.meta\" with ~tp", [Error]),
|
||||
ok = log(error, "Wrong directory?"),
|
||||
ok = log(error, "Read from zomp.meta failed with: ~tp", [Error]),
|
||||
Error
|
||||
end.
|
||||
|
||||
@ -268,10 +263,8 @@ read_project_meta(Dir) ->
|
||||
Result :: {ok, zx:package_meta()}
|
||||
| {error, file:posix()}.
|
||||
|
||||
read_package_meta({Realm, Name, Version}) ->
|
||||
{ok, VersionString} = version_to_string(Version),
|
||||
Path = filename:join([zomp_dir(), "lib", Realm, Name, VersionString]),
|
||||
read_project_meta(Path).
|
||||
read_package_meta(PackageID) ->
|
||||
read_project_meta(path(lib, PackageID)).
|
||||
|
||||
|
||||
-spec write_project_meta(Meta) -> Result
|
||||
@ -319,6 +312,21 @@ write_terms(Filename, List) ->
|
||||
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().
|
||||
%% @private
|
||||
%% Check whether a provided string is a valid lower0_9.
|
||||
@ -570,31 +578,6 @@ package_string(_) ->
|
||||
{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
|
||||
when PackageID :: zx:package_id(),
|
||||
ZrpFileName :: file:filename().
|
||||
@ -626,6 +609,12 @@ namify(PackageID, 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
|
||||
when Version :: 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.
|
||||
|
||||
installed(PackageID) ->
|
||||
filelib:is_dir(package_dir(PackageID)).
|
||||
filelib:is_dir(path(lib, PackageID)).
|
||||
|
||||
|
||||
|
||||
|
||||
-spec realm_conf(Realm) -> RealmFileName
|
||||
-spec realm_conf(Realm) -> Path
|
||||
when Realm :: string(),
|
||||
RealmFileName :: file:filename().
|
||||
Path :: file:filename().
|
||||
%% @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 ++ ".realm".
|
||||
filename:join(path(etc, Realm), "realm.conf").
|
||||
|
||||
|
||||
-spec load_realm_conf(Realm) -> Result
|
||||
when Realm :: zx:realm(),
|
||||
Result :: {ok, RealmConf}
|
||||
| {error, Reason},
|
||||
RealmConf :: list(),
|
||||
RealmConf :: map(),
|
||||
Reason :: badarg
|
||||
| terminated
|
||||
| system_limit
|
||||
@ -699,13 +686,13 @@ realm_conf(Realm) ->
|
||||
%% Load the config for the given realm or halt with an error.
|
||||
|
||||
load_realm_conf(Realm) ->
|
||||
Path = filename:join(path(etc, Realm), "realm.conf"),
|
||||
Path = realm_conf(Realm),
|
||||
case file:consult(Path) of
|
||||
{ok, C} ->
|
||||
C;
|
||||
{error, enoent} ->
|
||||
ok = log(warning, "Realm ~tp is not configured.", [Realm]),
|
||||
halt(1)
|
||||
{ok, maps:from_list(C)};
|
||||
Error ->
|
||||
ok = log(warning, "Loading realm conf ~ts failed with: ~tp", [Path, Error]),
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
@ -792,6 +779,30 @@ rm(Path) ->
|
||||
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
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
%%% Functions
|
||||
|
||||
-spec initialize(Type, PackageString) -> ok
|
||||
-spec initialize(Type, PackageString) -> zx:outcome()
|
||||
when Type :: app | lib,
|
||||
PackageString :: string().
|
||||
%% @private
|
||||
@ -33,32 +33,35 @@
|
||||
%% and interaction with the user to determine a few details.
|
||||
|
||||
initialize(Type, RawPackageString) ->
|
||||
ok =
|
||||
case filelib:is_file("zomp.meta") of
|
||||
false -> ok;
|
||||
true -> error_exit("This project is already Zompified.", ?LINE)
|
||||
end,
|
||||
PackageID = {Realm, Name, _} =
|
||||
false -> initialize2(Type, RawPackageString);
|
||||
true -> {error, "This project is already Zompified.", 17}
|
||||
end.
|
||||
|
||||
|
||||
initialize2(Type, RawPackageString) ->
|
||||
case zx_lib:package_id(RawPackageString) of
|
||||
{ok, {R, N, {z, z, z}}} ->
|
||||
{R, N, {0, 1, 0}};
|
||||
initialize3(Type, {R, N, {0, 1, 0}});
|
||||
{ok, {R, N, {X, z, z}}} ->
|
||||
{R, N, {X, 0, 0}};
|
||||
initialize3(Type, {R, N, {X, 0, 0}});
|
||||
{ok, {R, N, {X, Y, z}}} ->
|
||||
{R, N, {X, Y, 0}};
|
||||
initialize3(Type, {R, N, {X, Y, 0}});
|
||||
{ok, ID} ->
|
||||
ID;
|
||||
initialize3(Type, ID);
|
||||
{error, invalid_package_string} ->
|
||||
error_exit("Invalid package string: ~tp", [RawPackageString], ?LINE)
|
||||
end,
|
||||
{error, "Invalid package string.", 22}
|
||||
end.
|
||||
|
||||
|
||||
initialize3(Type, PackageID) ->
|
||||
case package_exists(PackageID) of
|
||||
false ->
|
||||
Prefix = solicit_prefix(),
|
||||
initialize(Type, PackageID, Prefix);
|
||||
true ->
|
||||
PackageName = zx_lib:package_string({Realm, Name, {z, z, z}}),
|
||||
Message = "Package ~tp already exists. Try another.",
|
||||
error_exit(Message, [PackageName], ?LINE)
|
||||
Message = "Package already exists. Try another.",
|
||||
{error, Message, 17}
|
||||
end.
|
||||
|
||||
|
||||
@ -161,6 +164,7 @@ solicit_prefix() ->
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
-spec update_source_vsn(zx:version()) -> ok.
|
||||
%% @private
|
||||
%% 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/*)",
|
||||
Add = lists:flatten(io_lib:format(AddF, [VersionString])),
|
||||
Sub = lists:flatten(io_lib:format(SubF, [VersionString])),
|
||||
ok = exec_shell(Add),
|
||||
ok = exec_shell(Sub),
|
||||
_ = os:cmd(Add),
|
||||
_ = os:cmd(Sub),
|
||||
log(info, "Source version attributes set").
|
||||
|
||||
|
||||
@ -240,22 +244,7 @@ initialize_app_file({_, Name, Version}, AppStart) ->
|
||||
zx_lib:write_terms(AppFile, [AppProfile]).
|
||||
|
||||
|
||||
-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) ->~n~ts", [CMD, Trimmed])
|
||||
end.
|
||||
|
||||
|
||||
-spec assimilate(PackageFile) -> ok
|
||||
-spec assimilate(PackageFile) -> zx:outcome()
|
||||
when PackageFile :: file:filename().
|
||||
%% @private
|
||||
%% 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),
|
||||
{KeyID, Signature} = maps:get(sig, Meta),
|
||||
{ok, PubKey} = zx_key:load(public, KeyID),
|
||||
ok =
|
||||
case public_key:verify(TgzData, sha512, Signature, PubKey) of
|
||||
true ->
|
||||
ZrpPath = filename:join("zsp", zx_lib:namify_zsp(PackageID)),
|
||||
file:copy(PackageFile, ZrpPath);
|
||||
ok = file:copy(PackageFile, zx_lib:zsp_path(PackageID)),
|
||||
assimilate2(CWD, PackageID);
|
||||
false ->
|
||||
error_exit("Bad package signature: ~ts", [PackageFile], ?LINE)
|
||||
end,
|
||||
{error, "Bad package signature.", 1}
|
||||
end.
|
||||
|
||||
|
||||
assimilate2(CWD, PackageID) ->
|
||||
ok = file:set_cwd(CWD),
|
||||
Message = "~ts is now locally available.",
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
log(info, Message, [PackageString]).
|
||||
|
||||
|
||||
-spec set_dep(Identifier :: string()) -> ok.
|
||||
-spec set_dep(Identifier :: string()) -> zx:outcome().
|
||||
%% @private
|
||||
%% 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
|
||||
@ -296,14 +287,12 @@ assimilate(PackageFile) ->
|
||||
|
||||
set_dep(Identifier) ->
|
||||
{ok, {Realm, Name, FuzzyVersion}} = zx_lib:package_id(Identifier),
|
||||
Version =
|
||||
case FuzzyVersion of
|
||||
{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])
|
||||
end,
|
||||
set_dep({Realm, Name}, Version).
|
||||
{error, "Incompelte version tuple.", 22}
|
||||
end.
|
||||
|
||||
|
||||
-spec set_dep(zx:package(), zx:version()) -> ok.
|
||||
@ -313,12 +302,8 @@ set_dep({Realm, Name}, Version) ->
|
||||
{ok, Meta} = zx_lib:read_project_meta(),
|
||||
Deps = maps:get(deps, Meta),
|
||||
case lists:member(PackageID, Deps) of
|
||||
true ->
|
||||
{ok, PackageString} = zx_lib:package_string(PackageID),
|
||||
ok = log(info, "~ts is already a dependency", [PackageString]),
|
||||
halt(0);
|
||||
false ->
|
||||
set_dep(PackageID, Deps, Meta)
|
||||
true -> ok;
|
||||
false -> set_dep(PackageID, Deps, Meta)
|
||||
end.
|
||||
|
||||
|
||||
@ -351,27 +336,25 @@ set_dep(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
|
||||
zx_lib:write_project_meta(NewMeta).
|
||||
|
||||
|
||||
-spec set_version(VersionString) -> ok
|
||||
-spec set_version(VersionString) -> zx:outcome()
|
||||
when VersionString :: string().
|
||||
%% @private
|
||||
%% Convert a version string to a new version, sanitizing it in the process and returning
|
||||
%% a reasonable error message on bad input.
|
||||
|
||||
set_version(VersionString) ->
|
||||
NewVersion =
|
||||
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} ->
|
||||
Message = "Invalid version string: ~tp",
|
||||
error_exit(Message, [VersionString], ?LINE)
|
||||
end,
|
||||
{error, Message, 22};
|
||||
{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(),
|
||||
{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
|
||||
@ -390,25 +373,31 @@ set_version(VersionString) ->
|
||||
update_version(Realm, Name, OldVersion, NewVersion, OldMeta) ->
|
||||
PackageID = {Realm, Name, NewVersion},
|
||||
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_app_vsn(Name, NewVersion),
|
||||
{ok, OldVS} = zx_lib:version_to_string(OldVersion),
|
||||
{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.
|
||||
%% @private
|
||||
%% List all currently configured realms. The definition of a "configured realm" is a
|
||||
%% realm for which a .realm file exists in $ZOMP_HOME. The realms will be printed to
|
||||
%% stdout and the program will exit.
|
||||
%% List all currently configured 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
|
||||
%% Contact the indicated realm and query it for a list of registered packages and print
|
||||
%% them to stdout.
|
||||
@ -416,62 +405,59 @@ list_realms() ->
|
||||
list_packages(Realm) ->
|
||||
case zx_daemon:list_packages(Realm) of
|
||||
{ok, []} ->
|
||||
log(info, "Realm ~tp has no packages available.", [Realm]);
|
||||
io:format("Realm ~tp has no packages available.~n", [Realm]);
|
||||
{ok, Packages} ->
|
||||
Print = fun({R, N}) -> io:format("~ts-~ts~n", [R, N]) end,
|
||||
lists:foreach(Print, Packages);
|
||||
{error, bad_realm} ->
|
||||
error_exit("Bad realm name.", ?LINE);
|
||||
{error, no_realm} ->
|
||||
error_exit("Realm \"~ts\" is not configured.", ?LINE);
|
||||
{error, "Unconfigured realm or bad realm name.", 22};
|
||||
{error, network} ->
|
||||
Message = "Network issues are preventing connection to the realm.",
|
||||
error_exit(Message, ?LINE)
|
||||
{error, Message, 101}
|
||||
end.
|
||||
|
||||
|
||||
-spec list_versions(PackageName :: string()) -> ok.
|
||||
-spec list_versions(PackageName :: string()) -> zx:outcome().
|
||||
%% @private
|
||||
%% 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
|
||||
%% of the form "otpr-zomp-1.2.3", one per line printed to stdout.
|
||||
|
||||
list_versions(PackageName) ->
|
||||
Package = {Realm, Name} =
|
||||
case zx_lib:package_id(PackageName) of
|
||||
{ok, {R, N, {z, z, z}}} ->
|
||||
{R, N};
|
||||
list_versions(PackageString) ->
|
||||
case zx_lib:package_id(PackageString) of
|
||||
{ok, PackageID} ->
|
||||
list_versions2(PackageID);
|
||||
{error, invalid_package_string} ->
|
||||
error_exit("~tp is not a valid package name.", [PackageName], ?LINE)
|
||||
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)
|
||||
{error, "Invalid package name.", 22}
|
||||
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().
|
||||
%% @private
|
||||
%% Add a .realm file to $ZOMP_HOME from a location in the filesystem.
|
||||
%% Print the SHA512 of the .realm file for the user so they can verify that the file
|
||||
%% is authentic. This implies, of course, that .realm maintainers are going to
|
||||
%% post SHA512 sums somewhere visible.
|
||||
%% Configure the system for a new realm by interpreting a .zrf file.
|
||||
%% Also log the SHA512 of the .zrf for the user.
|
||||
|
||||
add_realm(Path) ->
|
||||
case file:read_file(Path) of
|
||||
@ -479,13 +465,30 @@ add_realm(Path) ->
|
||||
Digest = crypto:hash(sha512, Data),
|
||||
Text = integer_to_list(binary:decode_unsigned(Digest, big), 16),
|
||||
ok = log(info, "SHA512 of ~ts: ~ts", [Path, Text]),
|
||||
add_realm(Path, Data);
|
||||
add_realm2(Data);
|
||||
{error, enoent} ->
|
||||
ok = log(warning, "FAILED: ~ts does not exist.", [Path]),
|
||||
halt(1);
|
||||
{error, "Realm bundle (.zrf) does not exist.", 2};
|
||||
{error, eisdir} ->
|
||||
ok = log(warning, "FAILED: ~ts is a directory, not a realm file.", [Path]),
|
||||
halt(1)
|
||||
{error, "Path is a directory, not a .zrf file.", 21}
|
||||
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.
|
||||
|
||||
|
||||
@ -516,7 +519,7 @@ drop_dep(PackageID) ->
|
||||
end.
|
||||
|
||||
|
||||
-spec verup(Level) -> ok
|
||||
-spec verup(Level) -> zx:outcome()
|
||||
when Level :: string().
|
||||
%% @private
|
||||
%% 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).
|
||||
|
||||
|
||||
-spec version_up(Level) -> ok
|
||||
-spec version_up(Level) -> zx:outcome()
|
||||
when Level :: major
|
||||
| minor
|
||||
| patch.
|
||||
@ -538,13 +541,13 @@ verup(_) -> zx:usage_exit(22).
|
||||
%% read for some reason.
|
||||
|
||||
version_up(Arg) ->
|
||||
Meta =
|
||||
case zx_lib:read_project_meta() of
|
||||
{ok, M} -> M;
|
||||
Error -> error_exit("verup failed with: ~tp", [Error], ?LINE)
|
||||
end,
|
||||
{ok, 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
|
||||
@ -582,7 +585,7 @@ package(TargetDir) ->
|
||||
ok = log(info, "Packaging ~ts", [TargetDir]),
|
||||
{ok, Meta} = zx_lib:read_project_meta(TargetDir),
|
||||
{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),
|
||||
Pattern = KeyDir ++ "/*.key.der",
|
||||
case [filename:basename(F, ".key.der") || F <- filelib:wildcard(Pattern)] of
|
||||
@ -712,12 +715,11 @@ dialyze() ->
|
||||
true -> log(info, "Using PLT: ~tp", [PLT]);
|
||||
false -> build_plt()
|
||||
end,
|
||||
TmpDir = filename:join(zx_lib:zomp_dir(), "tmp"),
|
||||
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]),
|
||||
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
|
||||
[] ->
|
||||
io:format("Dialyzer found no errors and returned no warnings! Yay!~n");
|
||||
@ -729,17 +731,14 @@ dialyze() ->
|
||||
file:delete(EvilTwin).
|
||||
|
||||
|
||||
-spec grow_a_pair() -> ok.
|
||||
-spec grow_a_pair() -> zx:outcome().
|
||||
%% @private
|
||||
%% Execute the key generation procedure for 16k RSA keys once and then terminate.
|
||||
|
||||
grow_a_pair() ->
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
KeyID = zx_key:prompt_keygen(),
|
||||
case zx_key:generate_rsa(KeyID) of
|
||||
ok -> ok;
|
||||
Error -> error_exit("grow_a_pair/0 error: ~tp", [Error], ?LINE)
|
||||
end.
|
||||
zx_key:generate_rsa(KeyID).
|
||||
|
||||
|
||||
-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).
|
||||
|
||||
drop_key({Realm, KeyName}) ->
|
||||
ok = file:set_cwd(zx_lib:zomp_dir()),
|
||||
KeyGlob = KeyName ++ ".{key,pub},der",
|
||||
Pattern = filename:join([zx_lib:zomp_dir(), "key", Realm, KeyGlob]),
|
||||
Pattern = filename:join(zx_lib:path(key, Realm), KeyName ++ ".{key,pub}.der"),
|
||||
case filelib:wildcard(Pattern) of
|
||||
[] ->
|
||||
log(warning, "Key ~ts/~ts not found", [Realm, KeyName]);
|
||||
@ -780,11 +777,12 @@ create_user(Realm, Username) ->
|
||||
%% realm file to the user.
|
||||
|
||||
create_realm() ->
|
||||
ok = log(info, "WOOHOO! Making a new realm!"),
|
||||
Instructions =
|
||||
"~n"
|
||||
" Enter a name for your new realm.~n"
|
||||
" Names can contain only lower-case letters, numbers and the underscore.~n"
|
||||
" Names must begin with a lower-case letter.~n",
|
||||
"~nNAMING~n"
|
||||
"Enter a name for your new realm.~n"
|
||||
"Names can contain only lower-case letters, numbers and the underscore.~n"
|
||||
"Names must begin with a lower-case letter.~n",
|
||||
ok = io:format(Instructions),
|
||||
Realm = zx_tty:get_input(),
|
||||
case zx_lib:valid_lower0_9(Realm) of
|
||||
@ -815,11 +813,11 @@ create_realm(Realm) ->
|
||||
|
||||
prompt_address() ->
|
||||
Message =
|
||||
"~n"
|
||||
" Enter a static, valid hostname or IPv4 or IPv6 address at which this host "
|
||||
"~nHOST ADDRESS~n"
|
||||
"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 "
|
||||
"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),
|
||||
case zx_tty:get_input() of
|
||||
"" ->
|
||||
@ -845,8 +843,8 @@ parse_address(String) ->
|
||||
|
||||
create_realm(Realm, Address) ->
|
||||
Message =
|
||||
"~n"
|
||||
" Enter the public (external) port number at which this service should be "
|
||||
"~nPUBLIC PORT NUMBER~n"
|
||||
"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 "
|
||||
"forwarding ports or have a complex network layout.)~n",
|
||||
ok = io:format(Message),
|
||||
@ -860,8 +858,8 @@ create_realm(Realm, Address) ->
|
||||
|
||||
prompt_port_number(Current) ->
|
||||
Instructions =
|
||||
" A valid port is any number from 1 to 65535."
|
||||
" [Press enter to accept the current setting: ~tw]~n",
|
||||
"A valid port is any number from 1 to 65535."
|
||||
"[Press enter to accept the current setting: ~tw]~n",
|
||||
ok = io:format(Instructions, [Current]),
|
||||
case zx_tty:get_input() of
|
||||
"" ->
|
||||
@ -891,10 +889,10 @@ prompt_port_number(Current) ->
|
||||
|
||||
create_realm(Realm, Address, Port) ->
|
||||
Instructions =
|
||||
"~n"
|
||||
" Enter a username for the realm sysop.~n"
|
||||
" Names can contain only lower-case letters, numbers and the underscore.~n"
|
||||
" Names must begin with a lower-case letter.~n",
|
||||
"~nSYSOP USERNAME~n"
|
||||
"Enter a username for the realm sysop.~n"
|
||||
"Names can contain only lower-case letters, numbers and the underscore.~n"
|
||||
"Names must begin with a lower-case letter.~n",
|
||||
ok = io:format(Instructions),
|
||||
UserName = zx_tty:get_input(),
|
||||
case zx_lib:valid_lower0_9(UserName) of
|
||||
@ -914,9 +912,9 @@ create_realm(Realm, Address, Port) ->
|
||||
|
||||
create_realm(Realm, Address, Port, UserName) ->
|
||||
Instructions =
|
||||
"~n"
|
||||
" Enter an email address for the realm sysop.~n"
|
||||
" Valid email address rules apply though the checking done here is quite "
|
||||
"~nSYSOP EMAIL~n"
|
||||
"Enter an email address for the realm sysop.~n"
|
||||
"Valid email address rules apply though the checking done here is quite "
|
||||
"minimal. Check the address you enter carefully. The only people who will "
|
||||
"suffer from an invalid address are your users.~n",
|
||||
ok = io:format(Instructions),
|
||||
@ -949,9 +947,9 @@ create_realm(Realm, Address, Port, UserName) ->
|
||||
|
||||
create_realm(Realm, Address, Port, UserName, Email) ->
|
||||
Instructions =
|
||||
"~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",
|
||||
"~nSYSOP REAL NAME~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",
|
||||
ok = io:format(Instructions),
|
||||
RealName = zx_tty:get_input(),
|
||||
create_realm(Realm, Address, Port, UserName, Email, RealName).
|
||||
@ -979,9 +977,8 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
|
||||
[{realm, Realm},
|
||||
{username, UserName},
|
||||
{realmname, RealName},
|
||||
{contact_info, {"email", Email}},
|
||||
{contact_info, [{"email", Email}]},
|
||||
{keys, [KeyName]}],
|
||||
ok = make_realm_dirs(Realm),
|
||||
RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"),
|
||||
ok = zx_lib:write_terms(RealmConfPath, RealmConf),
|
||||
UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"),
|
||||
@ -992,7 +989,7 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
|
||||
"===========================================================================~n"
|
||||
"DONE!~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"
|
||||
"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"
|
||||
@ -1015,9 +1012,9 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
|
||||
%% Checks for remnants of a realm.
|
||||
|
||||
realm_exists(Realm) ->
|
||||
Dirs = [etc, var, tmp, log, key, zsp, lib],
|
||||
Check = fun(D) -> filelib:is_file(zx_lib:path(D)) end,
|
||||
Managed = lists:member(Realm, proplists:get_value(managed, zx_lib:read_sys_conf())),
|
||||
Managed = lists:member(Realm, zx_sys_conf:managed(zx_sys_conf:load())),
|
||||
Dirs = [zx_lib:path(D, Realm) || D <- [etc, var, tmp, log, key, zsp, lib]],
|
||||
Check = fun(D) -> filelib:is_file(D) end,
|
||||
Found = lists:any(Check, Dirs),
|
||||
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.
|
||||
|
||||
make_realm_dirs(Realm) ->
|
||||
Dirs = [etc, var, tmp, log, key, zsp, lib],
|
||||
Make = fun(D) -> ok = file:make_dir(zx_lib:path(D, Realm)) end,
|
||||
Dirs = [zx_lib:path(D, Realm) || D <- [etc, var, tmp, log, key, zsp, lib]],
|
||||
Make = fun(D) -> ok = file:make_dir(D) end,
|
||||
lists:foreach(Make, Dirs).
|
||||
|
||||
|
||||
@ -1050,10 +1047,10 @@ configure_zomp() ->
|
||||
Dir :: file:filename().
|
||||
|
||||
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..."),
|
||||
KeyName = proplists:get_value(key, RealmConf),
|
||||
PubKeyPath = zx_key:name(pub, KeyName),
|
||||
KeyName = maps:get(key, RealmConf),
|
||||
PubKeyPath = zx_key:keypath(public, {Realm, KeyName}),
|
||||
{ok, PubDER} = file:read_file(PubKeyPath),
|
||||
Blob = term_to_binary({RealmConf, PubDER}),
|
||||
ZRF = filename:join(Dir, Realm ++ ".zrf"),
|
||||
@ -1061,22 +1058,6 @@ create_realmfile(Realm, Dir) ->
|
||||
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.
|
||||
|
||||
drop_realm(Realm) ->
|
||||
@ -1108,9 +1089,9 @@ drop_realm(Realm) ->
|
||||
%% the realm exists, of course.
|
||||
|
||||
takeover(Realm) ->
|
||||
SysConf = zx_conf_sys:load(),
|
||||
case zx_conf_sys:add_managed(Realm, SysConf) of
|
||||
{ok, NewConf} -> zx_conf_sys:save(NewConf);
|
||||
SysConf = zx_sys_conf:load(),
|
||||
case zx_sys_conf:add_managed(Realm, SysConf) of
|
||||
{ok, NewConf} -> zx_sys_conf:save(NewConf);
|
||||
{error, unconfigured} -> log(error, "Cannot take over an unconfigured realm.")
|
||||
end.
|
||||
|
||||
@ -1118,34 +1099,8 @@ takeover(Realm) ->
|
||||
-spec abdicate(zx:realm()) -> ok.
|
||||
|
||||
abdicate(Realm) ->
|
||||
SysConf = zx_conf_sys:load(),
|
||||
case zx_conf_sys:rem_managed(Realm, SysConf) of
|
||||
{ok, NewConf} -> zx_conf_sys:save(NewConf);
|
||||
SysConf = zx_sys_conf:load(),
|
||||
case zx_sys_conf:rem_managed(Realm, SysConf) of
|
||||
{ok, NewConf} -> zx_sys_conf:save(NewConf);
|
||||
{error, unmanaged} -> log(error, "Cannot abdicate an unmanaged realm.")
|
||||
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).
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
%%% @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
|
||||
%%% 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.
|
||||
%%% @end
|
||||
|
||||
-module(zx_conf_sys).
|
||||
-module(zx_sys_conf).
|
||||
-author("Craig Everett <zxq9@zxq9.com>").
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
Loading…
x
Reference in New Issue
Block a user