diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx.erl b/zomp/lib/otpr/zx/0.1.0/src/zx.erl index 5455d84..901422a 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx.erl @@ -64,7 +64,7 @@ type := app | lib}. -type outcome() :: ok - | {error, Reason :: atom()} + | {error, Reason :: term()} | {error, Code :: non_neg_integer()} | {error, Info :: string(), Code :: non_neg_integer()}. @@ -100,8 +100,10 @@ do(["list", "deps"]) -> done(zx_local:list_deps()); do(["list", "deps", PackageString]) -> done(zx_local:list_deps(PackageString)); -do(["install", PackageFile]) -> - done(zx_local:assimilate(PackageFile)); +do(["import", "zrp", PackageFile]) -> + done(zx_daemon:import_zrp(PackageFile)); +do(["install", PackageString]) -> + done(zx_daemon:install(PackageString)); do(["set", "dep", PackageString]) -> done(zx_local:set_dep(PackageString)); do(["set", "version", VersionString]) -> @@ -331,7 +333,7 @@ run(Identifier, RunArgs) -> end, {ok, PackageID} = ensure_installed(FuzzyID), ok = build(PackageID), - Dir = zx_lib:path(lib, PackageID), + Dir = zx_lib:ppath(lib, PackageID), {ok, Meta} = zx_lib:read_project_meta(Dir), prepare(PackageID, Meta, Dir, RunArgs). @@ -527,46 +529,13 @@ tuplize(String, Acc) -> end. - -%%% Package utilities - - --spec install(package_id()) -> ok. -%% @private -%% Install a package from the cache into the local system. -%% Before calling this function it must be known that: -%% - The zsp file is in the cache -%% - The zsp file is valid -%% - This function will only be called on startup by the launch process -%% - The package is not already installed -%% - If this function crashes it will completely halt the system - -install(PackageID = {Realm, Name, _}) -> - {ok, PackageString} = zx_lib:package_string(PackageID), - ok = log(info, "Installing ~ts", [PackageString]), - 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), - {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), - Meta = binary_to_term(MetaBin), - {KeyID, Signature} = maps:get(sig, Meta), - {ok, PubKey} = zx_key:load(public, KeyID), - ok = ensure_package_dirs(PackageID), - 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}]), - log(info, "~ts installed", [PackageString]). - - -spec build(package_id()) -> ok. %% @private %% Given an AppID, build the project from source and add it to the current lib path. build(PackageID) -> {ok, CWD} = file:get_cwd(), - ok = file:set_cwd(zx_lib:path(lib, PackageID)), + ok = file:set_cwd(zx_lib:ppath(lib, PackageID)), ok = zx_lib:build(), file:set_cwd(CWD). @@ -577,7 +546,7 @@ build(PackageID) -> %% run have been created or halt execution. ensure_package_dirs(PackageID) -> - Dirs = [zx_lib:path(D, PackageID) || D <- [etc, var, tmp, log, lib]], + Dirs = [zx_lib:ppath(D, PackageID) || D <- [etc, var, tmp, log, lib]], lists:foreach(fun zx_lib:force_dir/1, Dirs). @@ -642,10 +611,9 @@ usage() -> " zx reject PackageID~n" " zx add key Realm KeyName~n" " zx get key Realm KeyName~n" -" zx rem key Realm KeyName~n" " zx create user~n" " zx create userfiles Realm UserName~n" -" zx create keypair Realm~n" +" zx create keypair~n" " zx export user UserID~n" " zx import user ZdufFile~n" "~n" @@ -658,7 +626,6 @@ usage() -> " zx accept PackageID~n" " zx create realm~n" " zx create realmfile Realm~n" -" zx create sysop~n" "~n" "Where~n" " PackageID :: A string of the form Realm-Name[-Version]~n" diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl index 767de5c..f8edf06 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_auth.erl @@ -91,26 +91,24 @@ list_resigns(Realm) -> end. --spec submit(PackageFile) -> no_return() - when PackageFile :: file:filename(). +-spec submit(ZspPath :: file:filename()) -> zx:outcome(). %% @private %% Submit a package to the appropriate "prime" server for the given realm. -submit(PackageFile) -> - Files = zx_lib:extract_zsp_or_die(PackageFile), - {ok, PackageData} = file:read_file(PackageFile), - {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), - Meta = binary_to_term(MetaBin), - {Realm, Package, Version} = maps:get(package_id, Meta), - {ok, Socket} = connect_auth(Realm), - ok = send(Socket, {submit, {Realm, Package, Version}}), +submit(ZspPath) -> + {ok, ZspBin} = file:read_file(ZspPath), + <> = ZspBin, + <> = Signed, + {ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin), + {ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}), + true = zx_key:verify(Signed, Sig, PubKey), + ok = send(Socket, {submit, PackageID}), ok = recv_or_die(Socket), - ok = gen_tcp:send(Socket, PackageData), + ok = gen_tcp:send(Socket, ZspBin), ok = log(info, "Done sending contents of ~tp", [PackageFile]), Outcome = recv_or_die(Socket), log(info, "Response: ~tp", [Outcome]), - ok = disconnect(Socket), - halt(0). + disconnect(Socket). review(PackageString) -> diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl index 8428ef0..5a9b528 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl @@ -148,6 +148,7 @@ subscribe/1, unsubscribe/1, list/0, list/1, list/2, list/3, latest/1, fetch/1, verify_key/1, + install/1, import_zsp/1, pending/1, packagers/1, maintainers/1, sysops/1]). -export([report/1, result/2, notify/2]). -export([start_link/0, stop/0]). @@ -485,6 +486,27 @@ verify_key({Realm, KeyName}) -> request({verify_key, Realm, KeyName}). +-spec import_zsp(Path :: file:filename()) -> zx:outcome(). +%% @doc +%% Install a package from a local file. + +import_zsp(Path) -> + gen_server:call(?MODULE, {import_zsp, Path}). + + +-spec install(PackageString :: string()) -> zx:outcome(). +%% @doc +%% Install the specified package. + +install(PackageString) -> + case zx_lib:package_id(PackageString) of + {ok, PackageID} -> + gen_server:call(?MODULE, {install, PackageID}); + {error, invalid_package_string} -> + {error, "Invalid package string. Try again.", 22} + end. + + -spec pending(Package) -> {ok, RequestID} when Package :: zx:package(), RequestID :: id(). @@ -557,7 +579,7 @@ sysops(Realm) -> %% Private function to wrap the necessary bits up. request(Action) -> - gen_server:call(?MODULE, {request, self(), Action}). + gen_server:call(?MODULE, {request, Action}). @@ -689,12 +711,19 @@ stop() -> handle_call({request, list}, _, State = #s{cx = CX}) -> Realms = cx_realms(CX), {reply, {ok, Realms}, State}; -handle_call({request, Requestor, Action}, From, State = #s{id = ID}) -> +handle_call({request, Action}, From, State = #s{id = ID}) -> NewID = ID + 1, _ = gen_server:reply(From, {ok, NewID}), + Requestor = element(1, From), NextState = do_request(Requestor, Action, State#s{id = NewID}), NewState = eval_queue(NextState), {noreply, NewState}; +handle_call({install, PackageID}, _, State) -> + {Result, NewState} = do_install(PackageID, State), + {reply, Result, NewState}; +handle_call({import_zsp, Path}, _, State) -> + Result = do_import_zsp(Path), + {reply, Result, NewState}; handle_call(Unexpected, From, State) -> ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]), {noreply, State}. @@ -1208,6 +1237,86 @@ drop_requests(ReqIDs, Dropped, Requests) -> lists:fold(Partition, {Dropped, Requests}, ReqIDs). +-spec install(PackageID, State) -> {Result, NewState} + when PackageID :: zx:package_id(), + State :: state(), + Result :: zx:outcome(), + NewState :: state(). +%% @private +%% FIXME: This is about as useful as psuedocode at the moment. Meh. + +install(PackageID, State) -> + Path = zx_lib:zxp_path(PackageID), + case file:is_regular(Path) of + true -> + do_install(PackageID, Path); + false -> + ok = do_fetch(PackageID), + do_install(PackageID, Path) + end. + + +-spec do_import_zsp(file:filename()) -> zx:outcome(). +%% @private +%% Dealing with data from the (probably local) filesystem can fail in a bajillion ways +%% and spring memory leaks if one tries to get too clever. So I'm sidestepping all the +%% madness with a "try++" here: spawning a suicidal helper. + +do_import_zsp(Path) -> + {Pid, Mon} = spawn_monitor(fun() -> actually_import(Path) end), + receive + {Pid, Outcome} -> + true = demonitor(Mon, [flush]), + Outcome; + {'DOWN', Pid, process, Mon, Info} -> + {error, Info}; + after 5000 -> + {error, timeout} + end. + + +-spec actually_import(ZspPath) -> no_return() + when ZspPath :: file:filename(). +%% @private +%% The happy path of .zsp installation. +%% Must NEVER be executed by the zx_daemon directly. + +%% More generally, there are a few phases: +%% 1- Loading the binary to extract the PackageID +%% 2- Checking the signature +%% 3- Moving the file to the cache +%% 4- Wiping the destination directory +%% 5- Extracting the TarGz to the destination +%% Some combination of functions should make these steps happen in a way that isn't +%% totally ridiculous, OR the bullet should just be bitten an allow for the +%% redundant lines here and there in different package management functions. +%% +%% Use cases are: +%% - Install a missing package from upstream +%% - Install a missing package from the local cache +%% - Reinstall a package from the local cache +%% - Import a package to the cache from the local filesystem and install it +%% +%% The Correct Approach as determine by The Royal Me is that I'm going to accept the +%% redundant code in the short-term because the data format is already decided. +%% If a place to get more fancy with the phases becomes really obvious after writing +%% identicalish segements of functions a few places then I'll break things apart. + +actually_import(ZspPath) -> + {ok, Bin = <>} = file:read_file(ZspPath), + <> = Signed, + {ok, {PackageID, SigKeyName}} = zx_lib:b_to_ts(MetaBin), + {ok, PubKey} = zx_key:load(public, {element(1, PackageID), SigKeyName}), + true = zx_key:verify(Signed, Sig, PubKey), + ok = file:write_file(zx_lib:zsp_path(PackageID), Bin), + Destination = zx_lib:ppath(lib, PackageID), + ok = filelib:ensure_dir(Destination), + ok = zx_lib:rm_rf(Destination), + ok = file:make_dir(Destination), + ok = erl_tar:extract(TarGZ, [{cwd, Destination}]), + zx_daemon ! {self(), ok}. + + %%% Monitor Index ADT Interface Functions diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl index 8eac4f2..97e16ae 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl @@ -21,7 +21,7 @@ %%% Functions --spec ensure_keypair(zx:key_id()) -> true | no_return(). +-spec ensure_keypair(zx:key_id()) -> zx:outcome(). %% @private %% Check if both the public and private key based on KeyID exists. @@ -30,17 +30,17 @@ ensure_keypair(KeyID = {Realm, KeyName}) -> {true, true} -> true; {false, true} -> - Message = "Public key ~tp/~tp cannot be found", - ok = log(error, Message, [Realm, KeyName]), - halt(1); + Format = "Public key ~tp/~tp cannot be found", + Message = io_lib:format(Message, [Realm, KeyName]), + {error, Message, 2}; {true, false} -> - Message = "Private key ~tp/~tp cannot be found", - ok = log(error, Message, [Realm, KeyName]), - halt(1); + Format = "Private key ~tp/~tp cannot be found", + Message = io_lib:format(Message, [Realm, KeyName]), + {error, Message, 2}; {false, false} -> - Message = "Key pair ~tp/~tp cannot be found", - ok = log(error, Message, [Realm, KeyName]), - halt(1) + Format = "Key pair ~tp/~tp cannot be found", + Message = io_lib:format(Message, [Realm, KeyName]), + {error, Message, 2} end. @@ -120,39 +120,51 @@ generate_rsa(KeyID = {Realm, KeyName}) -> PubFile = path(public, KeyID), ok = lists:foreach(fun zx_lib:halt_if_exists/1, [PemFile, KeyFile, PubFile]), ok = log(info, "Generating ~p and ~p. Please be patient...", [KeyFile, PubFile]), - ok = gen_p_key(KeyFile), - ok = der_to_pem(KeyFile, PemFile), - {ok, PemBin} = file:read_file(PemFile), - [PemData] = public_key:pem_decode(PemBin), - Pub = public_key:pem_entry_decode(PemData), - PubDer = public_key:der_encode('RSAPublicKey', Pub), - ok = file:write_file(PubFile, PubDer), - case check_key(KeyFile, PubFile) of - true -> - ok = file:delete(PemFile), - log(info, "~ts and ~ts agree", [KeyFile, PubFile]); - false -> - ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]), - ok = log(error, "Something has gone wrong."), + case gen_p_key(KeyFile) of + ok -> + ok = der_to_pem(KeyFile, PemFile), + {ok, PemBin} = file:read_file(PemFile), + [PemData] = public_key:pem_decode(PemBin), + Pub = public_key:pem_entry_decode(PemData), + PubDer = public_key:der_encode('RSAPublicKey', Pub), + ok = file:write_file(PubFile, PubDer), + case check_key(KeyFile, PubFile) of + true -> + ok = file:delete(PemFile), + log(info, "~ts and ~ts agree", [KeyFile, PubFile]); + false -> + ok = lists:foreach(fun file:delete/1, [PemFile, KeyFile, PubFile]), + ok = log(error, "Something has gone wrong."), + {error, keygen_fail} + end; + {error, no_ssl} -> + ok = log(error, "OpenSSL not found."), {error, keygen_fail} end. --spec gen_p_key(KeyFile) -> ok - when KeyFile :: file:filename(). +-spec gen_p_key(KeyFile) -> Result + when KeyFile :: file:filename() + Result :: ok + | {error, no_ssl}. %% @private %% Format an openssl shell command that will generate proper 16k RSA keys. gen_p_key(KeyFile) -> - Command = - io_lib:format("~ts genpkey" - " -algorithm rsa" - " -out ~ts" - " -outform DER" - " -pkeyopt rsa_keygen_bits:16384", - [openssl(), KeyFile]), - Out = os:cmd(Command), - io:format(Out). + case openssl() of + {ok, OpenSSL} -> + Command = + io_lib:format("~ts genpkey" + " -algorithm rsa" + " -out ~ts" + " -outform DER" + " -pkeyopt rsa_keygen_bits:16384", + [OpenSSL, KeyFile]), + Out = os:cmd(Command), + io:format(Out); + Error -> + Error + end. -spec der_to_pem(KeyFile, PemFile) -> ok @@ -196,11 +208,14 @@ check_key(KeyFile, PubFile) -> public_key:verify(TestMessage, sha512, Signature, Pub). --spec openssl() -> Executable | no_return() - when Executable :: file:filename(). +-spec openssl() -> Result + when Result :: {ok, Executable} + | {error, no_ssl}, + Executable :: file:filename(). %% @private %% Attempt to locate the installed openssl executable for use in shell commands. -%% Halts execution with an error message if the executable cannot be found. +%% TODO: Determine whether it is even worth it to perform this check VS restricting +%% os:cmd/1 directed zx_key functions by platform. openssl() -> OpenSSL = @@ -208,16 +223,15 @@ openssl() -> {unix, _} -> "openssl"; {win32, _} -> "openssl.exe" end, - ok = - case os:find_executable(OpenSSL) of - false -> - ok = log(error, "OpenSSL could not be found in this system's PATH."), - ok = log(error, "Install OpenSSL and then retry."), - error_exit("Missing system dependenct: OpenSSL", ?LINE); - Path -> - log(info, "OpenSSL executable found at: ~ts", [Path]) - end, - OpenSSL. + case os:find_executable(OpenSSL) of + false -> + ok = log(error, "OpenSSL could not be found in this system's PATH."), + ok = log(error, "Install OpenSSL and then retry."), + {error, no_ssl}; + Path -> + log(info, "OpenSSL executable found at: ~ts", [Path]), + OpenSSL + end. -spec load(Type, KeyID) -> Result @@ -242,20 +256,16 @@ load(Type, KeyID) -> end. --spec verify(Data, Signature, PubKey) -> ok | no_return() +-spec verify(Data, Signature, PubKey) -> boolean() when Data :: binary(), Signature :: binary(), PubKey :: public_key:rsa_public_key(). %% @private -%% Verify the RSA Signature of some Data against the given PubKey or halt execution. -%% This function always assumes sha512 is the algorithm being used. -%% Should only ever be called by the initial launch process. +%% Curry out the choice of algorithm. This will probably disappear in a few more +%% versions as the details of sha512 and RSA gradually give way to the Brave New World. verify(Data, Signature, PubKey) -> - case public_key:verify(Data, sha512, Signature, PubKey) of - true -> ok; - false -> error_exit("Bad package signature!", ?LINE) - end. + public_key:verify(Data, sha512, Signature, PubKey). diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl index a394168..b882df5 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl @@ -25,11 +25,9 @@ valid_lower0_9/1, valid_label/1, valid_version/1, string_to_version/1, version_to_string/1, package_id/1, package_string/1, - namify_zsp/1, namify_tgz/1, - zsp_path/1, + namify_zsp/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, b_to_t/1, b_to_ts/1]). @@ -582,37 +580,17 @@ package_string(_) -> when PackageID :: zx:package_id(), ZrpFileName :: file:filename(). %% @private -%% Map an PackageID to its correct .zsp package file name. +%% Map a PackageID to its correct .zsp package file name. -namify_zsp(PackageID) -> namify(PackageID, "zsp"). - - --spec namify_tgz(PackageID) -> TgzFileName - when PackageID :: zx:package_id(), - TgzFileName :: file:filename(). -%% @private -%% Map an PackageID to its correct gzipped tarball source bundle filename. - -namify_tgz(PackageID) -> namify(PackageID, "tgz"). - - --spec namify(PackageID, Suffix) -> FileName - when PackageID :: zx:package_id(), - Suffix :: string(), - FileName :: file:filename(). -%% @private -%% Converts an PackageID to a canonical string, then appends the provided -%% filename Suffix. - -namify(PackageID, Suffix) -> +namify_zsp(PackageID) -> {ok, PackageString} = package_string(PackageID), - PackageString ++ "." ++ Suffix. + PackageString ++ ".zsp". -spec zsp_path(zx:package_id()) -> file:filename(). -zsp_path(PackageID) -> - filename:join(path(zsp, element(1, PackageID)), namify_zsp(PackageID)). +zsp_path({Realm, _, _}) -> + filename:join(path(zsp, Realm), namify_zsp(PackageID)). -spec find_latest_compatible(Version, Versions) -> Result @@ -683,7 +661,7 @@ realm_conf(Realm) -> | file:posix() | {Line :: integer(), Mod :: module(), Cause :: term()}. %% @private -%% Load the config for the given realm or halt with an error. +%% Load the config for the given realm. load_realm_conf(Realm) -> Path = realm_conf(Realm), @@ -696,41 +674,6 @@ load_realm_conf(Realm) -> end. --spec extract_zsp_or_die(FileName) -> Files | no_return() - when FileName :: file:filename(), - Files :: [{file:filename(), binary()}]. -%% @private -%% Extract a zsp archive, if possible. If not possible, halt execution with as accurate -%% an error message as can be managed. - -extract_zsp_or_die(FileName) -> - case erl_tar:extract(FileName, [memory]) of - {ok, Files} -> - Files; - {error, {FileName, enoent}} -> - Message = "Can't find file ~ts.", - error_exit(Message, [FileName], ?LINE); - {error, invalid_tar_checksum} -> - Message = "~ts is not a valid zsp archive.", - error_exit(Message, [FileName], ?LINE); - {error, Reason} -> - Message = "Extracting package file failed with: ~160tp.", - error_exit(Message, [Reason], ?LINE) - end. - - --spec halt_if_exists(file:filename()) -> ok | no_return(). -%% @private -%% A helper function to guard against overwriting an existing file. Halts execution if -%% the file is found to exist. - -halt_if_exists(Path) -> - case filelib:is_file(Path) of - true -> error_exit("~ts already exists! Halting.", [Path], ?LINE); - false -> ok - end. - - -spec build() -> ok. %% @private %% Run any local `zxmake' script needed by the project for non-Erlang code (if present), @@ -755,6 +698,7 @@ build() -> -spec rm_rf(file:filename()) -> ok | {error, file:posix()}. %% @private %% Recursively remove files and directories. Equivalent to `rm -rf'. +%% Does not return an error on a nonexistant path. rm_rf(Path) -> case filelib:is_dir(Path) of @@ -764,7 +708,10 @@ rm_rf(Path) -> ok = lists:foreach(fun rm/1, Contents), file:del_dir(Path); false -> - file:delete(Path) + case filelib:is_regular(Path) of + true -> file:delete(Path); + false -> ok + end end. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl index fbff670..643d8e7 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl @@ -10,7 +10,7 @@ -copyright("Craig Everett "). -license("GPL-3.0"). --export([initialize/2, assimilate/1, set_version/1, +-export([initialize/2, set_version/1, list_realms/0, list_packages/1, list_versions/1, set_dep/1, list_deps/0, list_deps/1, drop_dep/1, verup/1, package/1, add_realm/1, drop_realm/1, @@ -268,39 +268,6 @@ initialize_app_file({_, Name, Version}, AppStart) -> zx_lib:write_terms(AppFile, [AppProfile]). --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 -%% canonical location under a canonical name. - -assimilate(PackageFile) -> - Files = zx_lib:extract_zsp_or_die(PackageFile), - {ok, CWD} = file:get_cwd(), - ok = file:set_cwd(zx_lib:zomp_dir()), - {"zomp.meta", MetaBin} = lists:keyfind("zomp.meta", 1, Files), - Meta = binary_to_term(MetaBin), - PackageID = maps:get(package_id, Meta), - TgzFile = zx_lib:namify_tgz(PackageID), - {TgzFile, TgzData} = lists:keyfind(TgzFile, 1, Files), - {KeyID, Signature} = maps:get(sig, Meta), - {ok, PubKey} = zx_key:load(public, KeyID), - case public_key:verify(TgzData, sha512, Signature, PubKey) of - true -> - ok = file:copy(PackageFile, zx_lib:zsp_path(PackageID)), - assimilate2(CWD, PackageID); - false -> - {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_version(VersionString) -> zx:outcome() when VersionString :: string(). %% @private