Adjust dev scripts. Make interaction better.
This commit is contained in:
parent
aaf4ec124c
commit
1e6132f7e5
10
test_prep
Executable file
10
test_prep
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
pushd $(dirname $BASH_SOURCE) > /dev/null
|
||||||
|
ZX_DEV_ROOT=$PWD
|
||||||
|
popd > /dev/null
|
||||||
|
ZOMP_DIR="$ZX_DEV_ROOT/tester"
|
||||||
|
rm -rf "$ZOMP_DIR"
|
||||||
|
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
|
||||||
|
|
||||||
|
echo "Done. Make sure to export \"export ZOMP_DIR=$ZOMP_DIR\" before running zx"
|
||||||
@ -27,7 +27,7 @@
|
|||||||
-export_type([serial/0, package_id/0, package/0, realm/0, name/0, version/0,
|
-export_type([serial/0, package_id/0, package/0, realm/0, name/0, version/0,
|
||||||
identifier/0,
|
identifier/0,
|
||||||
host/0,
|
host/0,
|
||||||
key_id/0, key_name/0, key_data/0,
|
key_id/0, key_name/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,
|
||||||
@ -50,16 +50,13 @@
|
|||||||
-type host() :: {string() | inet:ip_address(), inet:port_number()}.
|
-type host() :: {string() | inet:ip_address(), inet:port_number()}.
|
||||||
-type key_id() :: {realm(), key_name()}.
|
-type key_id() :: {realm(), key_name()}.
|
||||||
-type key_name() :: lower0_9().
|
-type key_name() :: lower0_9().
|
||||||
-type key_data() :: {ID :: key_id(),
|
|
||||||
Public :: binary() | <<>>,
|
|
||||||
Private :: binary() | <<>>}.
|
|
||||||
-type user_id() :: {realm(), user_name()}.
|
-type user_id() :: {realm(), user_name()}.
|
||||||
-type user_name() :: label().
|
-type user_name() :: label().
|
||||||
-type contact_info() :: {Type :: string(), Data :: string()}.
|
-type contact_info() :: {Type :: string(), Data :: string()}.
|
||||||
-type user_data() :: {ID :: user_id(),
|
-type user_data() :: {ID :: user_id(),
|
||||||
RealName :: string(),
|
RealName :: string(),
|
||||||
Contact :: contact_info(),
|
Contact :: [contact_info()],
|
||||||
KeyData :: [key_data()]}.
|
Keys :: [key_name()]}.
|
||||||
-type lower0_9() :: [$a..$z | $0..$9 | $_].
|
-type lower0_9() :: [$a..$z | $0..$9 | $_].
|
||||||
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
||||||
-type package_meta() :: #{package_id := package_id(),
|
-type package_meta() :: #{package_id := package_id(),
|
||||||
@ -136,8 +133,8 @@ do(["package", TargetDir]) ->
|
|||||||
end;
|
end;
|
||||||
do(["dialyze"]) ->
|
do(["dialyze"]) ->
|
||||||
done(zx_local:dialyze());
|
done(zx_local:dialyze());
|
||||||
do(["create", "user", Realm, Name]) ->
|
do(["create", "user"]) ->
|
||||||
done(zx_local:create_user(Realm, Name));
|
done(zx_local:create_user());
|
||||||
do(["create", "keypair"]) ->
|
do(["create", "keypair"]) ->
|
||||||
done(zx_local:grow_a_pair());
|
done(zx_local:grow_a_pair());
|
||||||
do(["drop", "key", Realm, KeyName]) ->
|
do(["drop", "key", Realm, KeyName]) ->
|
||||||
@ -646,7 +643,7 @@ usage() ->
|
|||||||
" zx add key Realm KeyName~n"
|
" zx add key Realm KeyName~n"
|
||||||
" zx get key Realm KeyName~n"
|
" zx get key Realm KeyName~n"
|
||||||
" zx rem key Realm KeyName~n"
|
" zx rem key Realm KeyName~n"
|
||||||
" zx create user Realm~n"
|
" zx create user~n"
|
||||||
" zx create userfiles Realm UserName~n"
|
" zx create userfiles Realm UserName~n"
|
||||||
" zx create keypair Realm~n"
|
" zx create keypair Realm~n"
|
||||||
" zx export user UserID~n"
|
" zx export user UserID~n"
|
||||||
|
|||||||
@ -17,11 +17,26 @@
|
|||||||
takeover/1, abdicate/1,
|
takeover/1, abdicate/1,
|
||||||
create_plt/0, dialyze/0,
|
create_plt/0, dialyze/0,
|
||||||
grow_a_pair/0, drop_key/1,
|
grow_a_pair/0, drop_key/1,
|
||||||
create_user/2, create_realm/0, create_realmfile/2]).
|
create_user/0, create_realm/0, create_realmfile/2]).
|
||||||
|
|
||||||
-include("zx_logger.hrl").
|
-include("zx_logger.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
-record(user_data,
|
||||||
|
{realm = none :: none | zx:realm(),
|
||||||
|
username = none :: none | zx:user_name(),
|
||||||
|
realname = none :: none | string(),
|
||||||
|
contact_info = none :: none | [zx:contact_info()],
|
||||||
|
keys = none :: none | [zx:key_name()]}).
|
||||||
|
|
||||||
|
-record(realm_init,
|
||||||
|
{realm = none :: none | zx:realm(),
|
||||||
|
addr = none :: none | string() | inet:ip_address(),
|
||||||
|
port = none :: none | inet:port_number(),
|
||||||
|
sysop = none :: none | #user_data{},
|
||||||
|
url = none :: none | string()}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%% Functions
|
%%% Functions
|
||||||
|
|
||||||
@ -57,7 +72,7 @@ initialize2(Type, RawPackageString) ->
|
|||||||
initialize3(Type, PackageID) ->
|
initialize3(Type, PackageID) ->
|
||||||
case package_exists(PackageID) of
|
case package_exists(PackageID) of
|
||||||
false ->
|
false ->
|
||||||
Prefix = solicit_prefix(),
|
Prefix = ask_prefix(),
|
||||||
initialize(Type, PackageID, Prefix);
|
initialize(Type, PackageID, Prefix);
|
||||||
true ->
|
true ->
|
||||||
Message = "Package already exists. Try another.",
|
Message = "Package already exists. Try another.",
|
||||||
@ -139,11 +154,11 @@ package_exists(PackageID) ->
|
|||||||
false.
|
false.
|
||||||
|
|
||||||
|
|
||||||
-spec solicit_prefix() -> string().
|
-spec ask_prefix() -> string().
|
||||||
%% @private
|
%% @private
|
||||||
%% Get a valid module prefix to use as a namespace for new modules.
|
%% Get a valid module prefix to use as a namespace for new modules.
|
||||||
|
|
||||||
solicit_prefix() ->
|
ask_prefix() ->
|
||||||
Instructions =
|
Instructions =
|
||||||
"~nPICKING A PREFIX~n"
|
"~nPICKING A PREFIX~n"
|
||||||
"Most Erlang applications have a prefix on the front of their modules. This "
|
"Most Erlang applications have a prefix on the front of their modules. This "
|
||||||
@ -169,7 +184,7 @@ solicit_prefix() ->
|
|||||||
false ->
|
false ->
|
||||||
Message = "The string \"~tp\" is problematic. Try \"[a-z]*_\".~n",
|
Message = "The string \"~tp\" is problematic. Try \"[a-z]*_\".~n",
|
||||||
ok = io:format(Message, [Prefix]),
|
ok = io:format(Message, [Prefix]),
|
||||||
solicit_prefix()
|
ask_prefix()
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
@ -805,18 +820,6 @@ drop_key({Realm, KeyName}) ->
|
|||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec create_user(zx:realm(), zx:user_name()) -> ok.
|
|
||||||
%% @private
|
|
||||||
%% Validate the realm and username provided, prompt the user to either select a keypair
|
|
||||||
%% to use or generate a new one, and bundle a .zuser file for conveyance of the user
|
|
||||||
%% data and his relevant keys (for import into an existing zomp server via `add'
|
|
||||||
%% command like "add packager", "add maintainer" and "add sysop".
|
|
||||||
|
|
||||||
create_user(Realm, Username) ->
|
|
||||||
Message = "Would be generating a user file for {~160tp, ~160to}.",
|
|
||||||
log(info, Message, [Realm, Username]).
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm() -> ok.
|
-spec create_realm() -> ok.
|
||||||
%% @private
|
%% @private
|
||||||
%% Prompt the user to input the information necessary to create a new zomp realm,
|
%% Prompt the user to input the information necessary to create a new zomp realm,
|
||||||
@ -825,211 +828,58 @@ create_user(Realm, Username) ->
|
|||||||
|
|
||||||
create_realm() ->
|
create_realm() ->
|
||||||
ok = log(info, "WOOHOO! Making a new realm!"),
|
ok = log(info, "WOOHOO! Making a new realm!"),
|
||||||
|
create_realm(#realm_init{}).
|
||||||
|
|
||||||
|
|
||||||
|
create_realm(R = #realm_init{realm = none}) ->
|
||||||
|
create_realm(R#realm_init{realm = ask_realm()});
|
||||||
|
create_realm(R = #realm_init{addr = none}) ->
|
||||||
|
create_realm(R#realm_init{addr = ask_addr()});
|
||||||
|
create_realm(R = #realm_init{port = none}) ->
|
||||||
|
create_realm(R#realm_init{port = ask_port()});
|
||||||
|
create_realm(R = #realm_init{url = none}) ->
|
||||||
|
create_realm(R#realm_init{url = ask_url()});
|
||||||
|
create_realm(R = #realm_init{realm = Realm, sysop = none}) ->
|
||||||
|
create_realm(R#realm_init{sysop = create_sysop(#user_data{realm = Realm})});
|
||||||
|
create_realm(R = #realm_init{realm = Realm, addr = Addr, port = Port, url = URL}) ->
|
||||||
Instructions =
|
Instructions =
|
||||||
"~nNAMING~n"
|
"~nREALM DATA CONFIRMATION~n"
|
||||||
"Enter a name for your new realm.~n"
|
"That's everything! Please confirm these settings:~n"
|
||||||
"Names can contain only lower-case letters, numbers and the underscore.~n"
|
"[1] Realm Name : ~ts ~n"
|
||||||
"Names must begin with a lower-case letter.~n",
|
"[2] Host Address: ~ts~n"
|
||||||
ok = io:format(Instructions),
|
"[3] Port Number : ~w~n"
|
||||||
Realm = zx_tty:get_input(),
|
"[4] URL : ~ts~n"
|
||||||
case zx_lib:valid_lower0_9(Realm) of
|
"Press a number to select something to change, or [ENTER] to continue.~n",
|
||||||
true ->
|
ok = io:format(Instructions, [Realm, Addr, Port, URL]),
|
||||||
case realm_exists(Realm) of
|
|
||||||
false ->
|
|
||||||
create_realm(Realm);
|
|
||||||
true ->
|
|
||||||
ok = io:format("That realm already exists. Be more original.~n"),
|
|
||||||
create_realm()
|
|
||||||
end;
|
|
||||||
false ->
|
|
||||||
ok = io:format("Bad realm name \"~ts\". Try again.~n", [Realm]),
|
|
||||||
create_realm()
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm(Realm) -> ok
|
|
||||||
when Realm :: zx:realm().
|
|
||||||
|
|
||||||
create_realm(Realm) ->
|
|
||||||
Address = prompt_address(),
|
|
||||||
create_realm(Realm, Address).
|
|
||||||
|
|
||||||
|
|
||||||
-spec prompt_address() -> Result
|
|
||||||
when Result :: inet:hostname() | inet:ip_address().
|
|
||||||
|
|
||||||
prompt_address() ->
|
|
||||||
Message =
|
|
||||||
"~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",
|
|
||||||
ok = io:format(Message),
|
|
||||||
case zx_tty:get_input() of
|
case zx_tty:get_input() of
|
||||||
"" ->
|
"1" -> create_realm(R#realm_init{realm = none});
|
||||||
ok = io:format("You need to enter an address.~n"),
|
"2" -> create_realm(R#realm_init{addr = none});
|
||||||
prompt_address();
|
"3" -> create_realm(R#realm_init{port = none});
|
||||||
String ->
|
"4" -> create_realm(R#realm_init{url = none});
|
||||||
parse_address(String)
|
"" -> store_realm(R);
|
||||||
|
_ ->
|
||||||
|
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
|
||||||
|
create_realm(R)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec parse_address(string()) -> inet:hostname() | inet:ip_address().
|
store_realm(#realm_init{realm = Realm,
|
||||||
|
addr = Addr,
|
||||||
parse_address(String) ->
|
port = Port,
|
||||||
case inet:parse_address(String) of
|
url = URL,
|
||||||
{ok, Address} -> Address;
|
sysop = Sysop = #user_data{username = UserName,
|
||||||
{error, einval} -> String
|
keys = [KeyName]}}) ->
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm(Realm, Address) -> ok
|
|
||||||
when Realm :: zx:realm(),
|
|
||||||
Address :: inet:hostname() | inet:ip_address().
|
|
||||||
|
|
||||||
create_realm(Realm, Address) ->
|
|
||||||
Message =
|
|
||||||
"~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),
|
|
||||||
Port = prompt_port_number(11311),
|
|
||||||
create_realm(Realm, Address, Port).
|
|
||||||
|
|
||||||
|
|
||||||
-spec prompt_port_number(Current) -> Result
|
|
||||||
when Current :: inet:port_number(),
|
|
||||||
Result :: inet:port_number().
|
|
||||||
|
|
||||||
prompt_port_number(Current) ->
|
|
||||||
Instructions =
|
|
||||||
"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
|
|
||||||
"" ->
|
|
||||||
Current;
|
|
||||||
S ->
|
|
||||||
try
|
|
||||||
case list_to_integer(S) of
|
|
||||||
Port when 16#ffff >= Port, Port > 0 ->
|
|
||||||
Port;
|
|
||||||
Illegal ->
|
|
||||||
Whoops = "Whoops! ~tw is out of bounds (1~65535). Try again.~n",
|
|
||||||
ok = io:format(Whoops, [Illegal]),
|
|
||||||
prompt_port_number(Current)
|
|
||||||
end
|
|
||||||
catch error:badarg ->
|
|
||||||
ok = io:format("~tp is not a port number. Try again...", [S]),
|
|
||||||
prompt_port_number(Current)
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm(Realm, Address, Port) -> ok
|
|
||||||
when Realm :: zx:realm(),
|
|
||||||
Address :: inet:hostname() | inet:ip_address(),
|
|
||||||
Port :: inet:port_number(),
|
|
||||||
Port :: inet:port_number().
|
|
||||||
|
|
||||||
create_realm(Realm, Address, Port) ->
|
|
||||||
Instructions =
|
|
||||||
"~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
|
|
||||||
true ->
|
|
||||||
create_realm(Realm, Address, Port, UserName);
|
|
||||||
false ->
|
|
||||||
ok = io:format("Bad username ~tp. Try again.~n", [UserName]),
|
|
||||||
create_realm(Realm, Address, Port)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm(Realm, Address, Port, UserName) -> ok
|
|
||||||
when Realm :: zx:realm(),
|
|
||||||
Address :: inet:hostname() | inet:ip_address(),
|
|
||||||
Port :: inet:port_number(),
|
|
||||||
UserName :: string().
|
|
||||||
|
|
||||||
create_realm(Realm, Address, Port, UserName) ->
|
|
||||||
Instructions =
|
|
||||||
"~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),
|
|
||||||
Email = zx_tty:get_input(),
|
|
||||||
[User, Host] = string:lexemes(Email, "@"),
|
|
||||||
case {zx_lib:valid_lower0_9(User), zx_lib:valid_label(Host)} of
|
|
||||||
{true, true} ->
|
|
||||||
create_realm(Realm, Address, Port, UserName, Email);
|
|
||||||
{false, true} ->
|
|
||||||
Message = "The user part of the email address seems invalid. Try again.~n",
|
|
||||||
ok = io:format(Message),
|
|
||||||
create_realm(Realm, Address, Port, UserName);
|
|
||||||
{true, false} ->
|
|
||||||
Message = "The host part of the email address seems invalid. Try again.~n",
|
|
||||||
ok = io:format(Message),
|
|
||||||
create_realm(Realm, Address, Port, UserName);
|
|
||||||
{false, false} ->
|
|
||||||
Message = "This email address seems like its totally bonkers. Try again.~n",
|
|
||||||
ok = io:format(Message),
|
|
||||||
create_realm(Realm, Address, Port, UserName)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm(Realm, Address, Port, UserName, Email) -> ok
|
|
||||||
when Realm :: zx:realm(),
|
|
||||||
Address :: inet:hostname() | inet:ip_address(),
|
|
||||||
Port :: inet:port_number(),
|
|
||||||
UserName :: string(),
|
|
||||||
Email :: string().
|
|
||||||
|
|
||||||
create_realm(Realm, Address, Port, UserName, Email) ->
|
|
||||||
Instructions =
|
|
||||||
"~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).
|
|
||||||
|
|
||||||
|
|
||||||
-spec create_realm(Realm, Address, Port, UserName, Email, RealName) -> ok
|
|
||||||
when Realm :: zx:realm(),
|
|
||||||
Address :: inet:hostname() | inet:ip_address(),
|
|
||||||
Port :: inet:port_number(),
|
|
||||||
UserName :: string(),
|
|
||||||
Email :: string(),
|
|
||||||
RealName :: string().
|
|
||||||
|
|
||||||
create_realm(Realm, Address, Port, UserName, Email, RealName) ->
|
|
||||||
ok = io:format("~nGenerating keys. This might take a while, so settle in...~n"),
|
|
||||||
KeyName = UserName ++ "-root",
|
|
||||||
ok = make_realm_dirs(Realm),
|
ok = make_realm_dirs(Realm),
|
||||||
ok = zx_key:generate_rsa({Realm, KeyName}),
|
Address = parse_maybe_address(Addr),
|
||||||
RealmConf =
|
RealmConf =
|
||||||
[{realm, Realm},
|
[{realm, Realm},
|
||||||
{prime, {Address, Port}},
|
{prime, {Address, Port}},
|
||||||
{sysop, UserName},
|
{sysop, UserName},
|
||||||
{key, KeyName}],
|
{key, KeyName},
|
||||||
UserConf =
|
{url, URL}],
|
||||||
[{realm, Realm},
|
ok = store_user(Sysop),
|
||||||
{username, UserName},
|
|
||||||
{realmname, RealName},
|
|
||||||
{contact_info, [{"email", Email}]},
|
|
||||||
{keys, [KeyName]}],
|
|
||||||
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"),
|
|
||||||
ok = zx_lib:write_terms(UserConfPath, UserConf),
|
|
||||||
ok = create_realmfile(Realm, "."),
|
ok = create_realmfile(Realm, "."),
|
||||||
ZRF = Realm ++ ".zrf",
|
ZRF = Realm ++ ".zrf",
|
||||||
Message =
|
Message =
|
||||||
@ -1054,6 +904,308 @@ create_realm(Realm, Address, Port, UserName, Email, RealName) ->
|
|||||||
io:format(Message, Substitutions).
|
io:format(Message, Substitutions).
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_realm() -> string().
|
||||||
|
|
||||||
|
ask_realm() ->
|
||||||
|
Instructions =
|
||||||
|
"~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
|
||||||
|
true ->
|
||||||
|
case realm_exists(Realm) of
|
||||||
|
false ->
|
||||||
|
Realm;
|
||||||
|
true ->
|
||||||
|
ok = io:format("That realm already exists. Be more original.~n"),
|
||||||
|
ask_realm()
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
ok = io:format("Bad realm name \"~ts\". Try again.~n", [Realm]),
|
||||||
|
ask_realm()
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_addr() -> string().
|
||||||
|
|
||||||
|
ask_addr() ->
|
||||||
|
Message =
|
||||||
|
"~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",
|
||||||
|
ok = io:format(Message),
|
||||||
|
case zx_tty:get_input() of
|
||||||
|
"" ->
|
||||||
|
ok = io:format("You need to enter an address.~n"),
|
||||||
|
ask_addr();
|
||||||
|
String ->
|
||||||
|
String
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec parse_maybe_address(string()) -> inet:hostname() | inet:ip_address().
|
||||||
|
|
||||||
|
parse_maybe_address(String) ->
|
||||||
|
case inet:parse_address(String) of
|
||||||
|
{ok, Address} -> Address;
|
||||||
|
{error, einval} -> String
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_port() -> inet:port_number().
|
||||||
|
|
||||||
|
ask_port() ->
|
||||||
|
Message =
|
||||||
|
"~nPUBLIC PORT NUMBER~n"
|
||||||
|
"Enter the publicly visible (external) port number at which this service "
|
||||||
|
"should be available.~n"
|
||||||
|
"(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),
|
||||||
|
prompt_port_number(11311).
|
||||||
|
|
||||||
|
|
||||||
|
-spec prompt_port_number(Current) -> Result
|
||||||
|
when Current :: inet:port_number(),
|
||||||
|
Result :: inet:port_number().
|
||||||
|
|
||||||
|
prompt_port_number(Current) ->
|
||||||
|
ok = io:format("A valid port is any number from 1 to 65535.~n"),
|
||||||
|
case zx_tty:get_input("[~tw]", [Current]) of
|
||||||
|
"" ->
|
||||||
|
Current;
|
||||||
|
S ->
|
||||||
|
try
|
||||||
|
case list_to_integer(S) of
|
||||||
|
Port when 16#ffff >= Port, Port > 0 ->
|
||||||
|
Port;
|
||||||
|
Illegal ->
|
||||||
|
Whoops = "Whoops! ~tw is out of bounds (1~65535). Try again.~n",
|
||||||
|
ok = io:format(Whoops, [Illegal]),
|
||||||
|
prompt_port_number(Current)
|
||||||
|
end
|
||||||
|
catch error:badarg ->
|
||||||
|
ok = io:format("~tp is not a port number. Try again...", [S]),
|
||||||
|
prompt_port_number(Current)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_url() -> string().
|
||||||
|
|
||||||
|
ask_url() ->
|
||||||
|
Message =
|
||||||
|
"~nURL~n"
|
||||||
|
"Most public realms have a website, IRC channel, or similar location where its "
|
||||||
|
"community (or customers) communicate. If you have such a URL enter it here.~n"
|
||||||
|
"NOTE: No checking is performed on the input here. Confuse your users at "
|
||||||
|
"your own peril!~n",
|
||||||
|
ok = io:format(Message),
|
||||||
|
zx_tty:get_input("[ENTER] to leave blank").
|
||||||
|
|
||||||
|
|
||||||
|
-spec create_sysop(InitUser) -> FullUser
|
||||||
|
when InitUser :: #user_data{},
|
||||||
|
FullUser :: #user_data{}.
|
||||||
|
|
||||||
|
create_sysop(U = #user_data{username = none}) ->
|
||||||
|
UserName = ask_username(),
|
||||||
|
KeyName = UserName ++ ".root",
|
||||||
|
create_sysop(U#user_data{username = UserName, keys = [KeyName]});
|
||||||
|
create_sysop(U = #user_data{realname = none}) ->
|
||||||
|
create_sysop(U#user_data{realname = ask_realname()});
|
||||||
|
create_sysop(U = #user_data{contact_info = none}) ->
|
||||||
|
create_sysop(U#user_data{contact_info = [ask_email()]});
|
||||||
|
create_sysop(U = #user_data{username = UserName,
|
||||||
|
realname = RealName,
|
||||||
|
contact_info = [{"email", Email}]}) ->
|
||||||
|
Instructions =
|
||||||
|
"~nSYSOP DATA CONFIRMATION~n"
|
||||||
|
"Please correct or confirm the sysop's data:~n"
|
||||||
|
"[1] Username : ~ts~n"
|
||||||
|
"[2] Real Name: ~ts~n"
|
||||||
|
"[3] Email : ~ts~n"
|
||||||
|
"Press a number to select something to change, or [ENTER] to accept.~n",
|
||||||
|
ok = io:format(Instructions, [UserName, RealName, Email]),
|
||||||
|
case zx_tty:get_input() of
|
||||||
|
"1" -> create_sysop(U#user_data{username = none});
|
||||||
|
"2" -> create_sysop(U#user_data{realname = none});
|
||||||
|
"3" -> create_sysop(U#user_data{contact_info = none});
|
||||||
|
"" -> U;
|
||||||
|
_ ->
|
||||||
|
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
|
||||||
|
create_sysop(U)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec create_user() -> zx:outcome().
|
||||||
|
|
||||||
|
create_user() ->
|
||||||
|
create_user(#user_data{}).
|
||||||
|
|
||||||
|
|
||||||
|
-spec create_user(#user_data{}) -> zx:outcome().
|
||||||
|
|
||||||
|
create_user(U = #user_data{realm = none}) ->
|
||||||
|
case pick_realm() of
|
||||||
|
{ok, Realm} -> create_user(U#user_data{realm = Realm});
|
||||||
|
{error, no_realms} -> {error, "No realms configured.", 1}
|
||||||
|
end;
|
||||||
|
create_user(U = #user_data{username = none}) ->
|
||||||
|
UserName = ask_username(),
|
||||||
|
KeyName = UserName ++ ".1",
|
||||||
|
create_user(U#user_data{username = UserName, keys = [KeyName]});
|
||||||
|
create_user(U = #user_data{realname = none}) ->
|
||||||
|
create_user(U#user_data{realname = ask_realname()});
|
||||||
|
create_user(U = #user_data{contact_info = none}) ->
|
||||||
|
create_user(U#user_data{contact_info = [ask_email()]});
|
||||||
|
create_user(U = #user_data{realm = Realm,
|
||||||
|
username = UserName,
|
||||||
|
realname = RealName,
|
||||||
|
contact_info = [{"email", Email}]}) ->
|
||||||
|
Instructions =
|
||||||
|
"~nUSER DATA CONFIRMATION~n"
|
||||||
|
"Please correct or confirm the user's data:~n"
|
||||||
|
"[1] Realm : ~ts~n"
|
||||||
|
"[2] Username : ~ts~n"
|
||||||
|
"[3] Real Name: ~ts~n"
|
||||||
|
"[4] Email : ~ts~n"
|
||||||
|
"Press a number to select something to change, or [ENTER] to accept.~n",
|
||||||
|
ok = io:format(Instructions, [Realm, UserName, RealName, Email]),
|
||||||
|
case zx_tty:get_input() of
|
||||||
|
"1" -> create_user(U#user_data{realm = none});
|
||||||
|
"2" -> create_user(U#user_data{username = none});
|
||||||
|
"3" -> create_user(U#user_data{realname = none});
|
||||||
|
"4" -> create_user(U#user_data{contact_info = none});
|
||||||
|
"" -> store_user(U);
|
||||||
|
_ ->
|
||||||
|
ok = io:format("~nArglebargle, glop-glyf!?!~n~n"),
|
||||||
|
create_user(U)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec store_user(#user_data{}) -> ok.
|
||||||
|
|
||||||
|
store_user(#user_data{realm = Realm,
|
||||||
|
username = UserName,
|
||||||
|
realname = RealName,
|
||||||
|
contact_info = ContactInfo,
|
||||||
|
keys = KeyNames}) ->
|
||||||
|
UserConf =
|
||||||
|
[{realm, Realm},
|
||||||
|
{username, UserName},
|
||||||
|
{realname, RealName},
|
||||||
|
{contact_info, ContactInfo},
|
||||||
|
{keys, KeyNames}],
|
||||||
|
ok = gen_keys(Realm, KeyNames),
|
||||||
|
UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"),
|
||||||
|
ok = zx_lib:write_terms(UserConfPath, UserConf),
|
||||||
|
log(info, "User ~tp created.", [{Realm, UserName}]).
|
||||||
|
|
||||||
|
|
||||||
|
-spec gen_keys(Realm, KeyNames) -> ok
|
||||||
|
when Realm :: zx:realm(),
|
||||||
|
KeyNames :: [zx:key_names()].
|
||||||
|
|
||||||
|
gen_keys(Realm, KeyNames) ->
|
||||||
|
ok = io:format("Generating keys. This might take a while, so settle in...~n"),
|
||||||
|
GenRSA = fun(KeyName) -> zx_key:generate_rsa({Realm, KeyName}) end,
|
||||||
|
lists:foreach(GenRSA, KeyNames).
|
||||||
|
|
||||||
|
|
||||||
|
-spec pick_realm() -> Result
|
||||||
|
when Result :: {ok, zx:realm()}
|
||||||
|
| {error, no_realms}.
|
||||||
|
|
||||||
|
pick_realm() ->
|
||||||
|
case zx_lib:list_realms() of
|
||||||
|
[] ->
|
||||||
|
ok = log(warning, "No realms configured! Exiting..."),
|
||||||
|
{error, no_realms};
|
||||||
|
Realms ->
|
||||||
|
ok = io:format("Select a realm:~n"),
|
||||||
|
Options = lists:zip(Realms, Realms),
|
||||||
|
Realm = zx_tty:select(Options),
|
||||||
|
{ok, Realm}
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_username() -> zx:user_name().
|
||||||
|
|
||||||
|
ask_username() ->
|
||||||
|
Instructions =
|
||||||
|
"~nUSERNAME~n"
|
||||||
|
"Enter a username.~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),
|
||||||
|
case zx_tty:get_input() of
|
||||||
|
"" ->
|
||||||
|
Message = "You have to be called *something*. Let's try that again.~n",
|
||||||
|
ok = io:format(Message),
|
||||||
|
ask_username();
|
||||||
|
UserName ->
|
||||||
|
case zx_lib:valid_lower0_9(UserName) of
|
||||||
|
true ->
|
||||||
|
UserName;
|
||||||
|
false ->
|
||||||
|
ok = io:format("Bad username ~tp. Try again.~n", [UserName]),
|
||||||
|
ask_username()
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_realname() -> string().
|
||||||
|
|
||||||
|
ask_realname() ->
|
||||||
|
Instructions =
|
||||||
|
"~nREAL NAME~n"
|
||||||
|
"Enter the user's real name (or whatever name people recognize).~n"
|
||||||
|
"There are no rules for this one. Any valid UTF-8 printables are legal.~n",
|
||||||
|
ok = io:format(Instructions),
|
||||||
|
zx_tty:get_input().
|
||||||
|
|
||||||
|
|
||||||
|
-spec ask_email() -> {Type :: string(), Email :: string()}.
|
||||||
|
|
||||||
|
ask_email() ->
|
||||||
|
Instructions =
|
||||||
|
"~nEMAIL~n"
|
||||||
|
"Enter an email address.~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 other realm users.~n",
|
||||||
|
ok = io:format(Instructions),
|
||||||
|
Email = zx_tty:get_input(),
|
||||||
|
case string:lexemes(Email, "@") of
|
||||||
|
[User, Host] ->
|
||||||
|
case {zx_lib:valid_lower0_9(User), zx_lib:valid_label(Host)} of
|
||||||
|
{true, true} ->
|
||||||
|
{"email", Email};
|
||||||
|
{false, true} ->
|
||||||
|
Message = "The user part of the email address seems invalid.~n",
|
||||||
|
ok = io:format(Message),
|
||||||
|
ask_email();
|
||||||
|
{true, false} ->
|
||||||
|
Message = "The host part of the email address seems invalid.~n",
|
||||||
|
ok = io:format(Message),
|
||||||
|
ask_email();
|
||||||
|
{false, false} ->
|
||||||
|
Message = "This email address is totally bonkers. Try again.~n",
|
||||||
|
ok = io:format(Message),
|
||||||
|
ask_email()
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok = io:format("Don't get fresh with me. Try again. For real this time.~n"),
|
||||||
|
ask_email()
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec realm_exists(zx:realm()) -> boolean().
|
-spec realm_exists(zx:realm()) -> boolean().
|
||||||
%% @private
|
%% @private
|
||||||
%% Checks for remnants of a realm.
|
%% Checks for remnants of a realm.
|
||||||
@ -1111,8 +1263,7 @@ drop_realm(Realm) ->
|
|||||||
case realm_exists(Realm) of
|
case realm_exists(Realm) of
|
||||||
true ->
|
true ->
|
||||||
Message =
|
Message =
|
||||||
"~n"
|
"~nWARNING: Are you SURE you want to remove realm ~ts?~n"
|
||||||
" WARNING: Are you SURE you want to remove realm ~ts?~n"
|
|
||||||
"(Only \"Y\" will confirm this action.)~n",
|
"(Only \"Y\" will confirm this action.)~n",
|
||||||
ok = io:format(Message, [Realm]),
|
ok = io:format(Message, [Realm]),
|
||||||
case zx_tty:get_input() of
|
case zx_tty:get_input() of
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||||
-license("GPL-3.0").
|
-license("GPL-3.0").
|
||||||
|
|
||||||
-export([get_input/0, select/1, select_string/1]).
|
-export([get_input/0, get_input/1, get_input/2, select/1, select_string/1]).
|
||||||
|
|
||||||
|
|
||||||
%%% Type Definitions
|
%%% Type Definitions
|
||||||
@ -20,12 +20,38 @@
|
|||||||
|
|
||||||
%%% User menu interface (terminal)
|
%%% User menu interface (terminal)
|
||||||
|
|
||||||
|
|
||||||
-spec get_input() -> string().
|
-spec get_input() -> string().
|
||||||
%% @private
|
%% @private
|
||||||
%% Provide a standard input prompt and newline sanitized return value.
|
%% Provide a standard input prompt and newline sanitized return value.
|
||||||
|
|
||||||
get_input() ->
|
get_input() ->
|
||||||
string:trim(io:get_line("(^C to quit): ")).
|
case string:trim(io:get_line("(or \"QUIT\"): ")) of
|
||||||
|
"QUIT" -> what_a_quitter();
|
||||||
|
String -> String
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
-spec get_input(Prompt :: string()) -> string().
|
||||||
|
%% @private
|
||||||
|
%% Introduce
|
||||||
|
|
||||||
|
get_input(Prompt) ->
|
||||||
|
get_input(Prompt, []).
|
||||||
|
|
||||||
|
|
||||||
|
-spec get_input(Format, Args) -> string()
|
||||||
|
when Format :: string(),
|
||||||
|
Args :: [term()].
|
||||||
|
%% @private
|
||||||
|
%% Allow the caller to use io format strings and args to create a prompt.
|
||||||
|
|
||||||
|
get_input(Format, Args) ->
|
||||||
|
Prompt = io_lib:format(Format, Args),
|
||||||
|
case string:trim(io:get_line(["(or \"QUIT\") ", Prompt, ": "])) of
|
||||||
|
"QUIT" -> what_a_quitter();
|
||||||
|
String -> String
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
-spec select(Options) -> Selected
|
-spec select(Options) -> Selected
|
||||||
@ -37,13 +63,18 @@ get_input() ->
|
|||||||
|
|
||||||
select(Options) ->
|
select(Options) ->
|
||||||
Max = show(Options),
|
Max = show(Options),
|
||||||
case pick(string:to_integer(io:get_line("(or ^C to quit)~n ? ")), Max) of
|
case string:trim(io:get_line("(or \"QUIT\"): ")) of
|
||||||
|
"QUIT" ->
|
||||||
|
what_a_quitter();
|
||||||
|
String ->
|
||||||
|
case pick(string:to_integer(String), Max) of
|
||||||
error ->
|
error ->
|
||||||
ok = hurr(),
|
ok = hurr(),
|
||||||
select(Options);
|
select(Options);
|
||||||
I ->
|
Index ->
|
||||||
{_, Value} = lists:nth(I, Options),
|
{_, Value} = lists:nth(Index, Options),
|
||||||
Value
|
Value
|
||||||
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@ -78,7 +109,7 @@ show([], I) ->
|
|||||||
I;
|
I;
|
||||||
show([{Label, _} | Rest], I) ->
|
show([{Label, _} | Rest], I) ->
|
||||||
Z = I + 1,
|
Z = I + 1,
|
||||||
ok = io:format(" ~2w - ~ts~n", [Z, Label]),
|
ok = io:format("[~2w] ~ts~n", [Z, Label]),
|
||||||
show(Rest, Z).
|
show(Rest, Z).
|
||||||
|
|
||||||
|
|
||||||
@ -99,3 +130,12 @@ pick(_, _) -> error.
|
|||||||
%% Present an appropriate response when the user derps on selection.
|
%% Present an appropriate response when the user derps on selection.
|
||||||
|
|
||||||
hurr() -> io:format("That isn't an option.~n").
|
hurr() -> io:format("That isn't an option.~n").
|
||||||
|
|
||||||
|
|
||||||
|
-spec what_a_quitter() -> no_return().
|
||||||
|
%% @private
|
||||||
|
%% Halt the runtime if the user decides to quit.
|
||||||
|
|
||||||
|
what_a_quitter() ->
|
||||||
|
ok = io:format("User abort: \"QUIT\".~nHalting.~n"),
|
||||||
|
halt(0).
|
||||||
|
|||||||
13
zomp/zx
13
zomp/zx
@ -1,10 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
ZOMP_DIR="$HOME/.zomp"
|
export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}"
|
||||||
VERSION=$(cat "$ZOMP_DIR/etc/version.txt")
|
version=$(cat "$ZOMP_DIR/etc/version.txt")
|
||||||
ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION"
|
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version"
|
||||||
|
|
||||||
pushd "$ZX_DIR" > /dev/null
|
start_dir="$PWD"
|
||||||
|
cd "$ZX_DIR"
|
||||||
./make_zx
|
./make_zx
|
||||||
popd > /dev/null
|
cd "$start_dir"
|
||||||
erl -pa "$ZX_DIR/ebin" -run zx run $@
|
erl -noshell -pa "$ZX_DIR/ebin" -run zx do $@
|
||||||
|
|||||||
11
zomp/zxh
Executable file
11
zomp/zxh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
export ZOMP_DIR="${ZOMP_DIR:-$HOME/.zomp}"
|
||||||
|
version=$(cat "$ZOMP_DIR/etc/version.txt")
|
||||||
|
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$version"
|
||||||
|
|
||||||
|
start_dir="$PWD"
|
||||||
|
cd "$ZX_DIR"
|
||||||
|
./make_zx
|
||||||
|
cd "$start_dir"
|
||||||
|
erl -pa "$ZX_DIR/ebin" -run zx do $@
|
||||||
9
zomp/zxh.cmd
Normal file
9
zomp/zxh.cmd
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
REM Prepare the environment for ZX and launch it
|
||||||
|
|
||||||
|
set ZOMP_DIR="%LOCALAPPDATA%\zomp"
|
||||||
|
set VERSION=<"%ZOMP_DIR%\etc\version.txt"
|
||||||
|
set ZX_DIR="%ZOMP_DIR%\lib\otpr\zx\%VERSION%"
|
||||||
|
pushd "%ZX_DIR%"
|
||||||
|
escript.exe make_zx
|
||||||
|
popd
|
||||||
|
erl.exe -pa "%ZX_DIR%/ebin" -run zx run "%*"
|
||||||
15
zx_dev
15
zx_dev
@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
pushd $(dirname $BASH_SOURCE) > /dev/null
|
|
||||||
ZX_DEV_ROOT=$PWD
|
|
||||||
popd > /dev/null
|
|
||||||
export ZOMP_DIR="$ZX_DEV_ROOT/tester"
|
|
||||||
rm -rf "$ZOMP_DIR"
|
|
||||||
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
|
|
||||||
VERSION=$(cat "$ZOMP_DIR/etc/version.txt")
|
|
||||||
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION"
|
|
||||||
|
|
||||||
pushd "$ZX_DIR" > /dev/null
|
|
||||||
./make_zx
|
|
||||||
popd > /dev/null
|
|
||||||
erl -noshell -pa "$ZX_DIR/ebin" -run zx do $@
|
|
||||||
15
zxh_dev
15
zxh_dev
@ -1,15 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
pushd $(dirname $BASH_SOURCE) > /dev/null
|
|
||||||
ZX_DEV_ROOT=$PWD
|
|
||||||
popd > /dev/null
|
|
||||||
export ZOMP_DIR="$ZX_DEV_ROOT/tester"
|
|
||||||
rm -rf "$ZOMP_DIR"
|
|
||||||
cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR"
|
|
||||||
VERSION=$(cat "$ZOMP_DIR/etc/version.txt")
|
|
||||||
export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION"
|
|
||||||
|
|
||||||
pushd "$ZX_DIR" > /dev/null
|
|
||||||
./make_zx
|
|
||||||
popd > /dev/null
|
|
||||||
erl -pa "$ZX_DIR/ebin" -run zx do $@
|
|
||||||
Loading…
x
Reference in New Issue
Block a user