diff --git a/zx b/zx index 6bfc07b..6f83b5b 100755 --- a/zx +++ b/zx @@ -1404,8 +1404,10 @@ dialyze() -> create_realm() -> ConfFile = filename:join(zomp_dir(), "zomp.conf"), - {ok, ZompConf} = file:consult(ConfFile), - create_realm(ZompConf). + case file:consult(ConfFile) of + {ok, ZompConf} -> create_realm(ZompConf); + {error, enoent} -> create_realm([]) + end. create_realm(ZompConf) -> Instructions = @@ -1426,107 +1428,115 @@ create_realm(ZompConf) -> create_realm(ZompConf) end; false -> - ok = io:format("Bad realm name ~tp. Try again.~n", [Realm]), + ok = io:format("Bad realm name \"~ts\". Try again.~n", [Realm]), create_realm(ZompConf) end. create_realm(ZompConf, Realm) -> - {external_address, XA} = lists:keyfind(external_address, 1, ZompConf), + ExAddress = + case lists:keyfind(external_address, 1, ZompConf) of + false -> prompt_external_address(); + {external_address, none} -> prompt_external_address(); + {external_address, Current} -> prompt_external_address(Current) + end, + create_realm(ZompConf, Realm, ExAddress). + +prompt_external_address() -> + Message = external_address_prompt(), + ok = io:format(Message), + case get_input() of + "" -> + ok = io:format("You need to enter an address.~n"), + prompt_external_address(); + String -> + parse_address(String) + end. + +prompt_external_address(Current) -> XAString = - case inet:ntoa(XA) of - {error, einval} -> XA; - String -> String + case inet:ntoa(Current) of + {error, einval} -> Current; + XAS -> XAS + end, + Message = + external_address_prompt() ++ + " [The current public address is: ~ts. Press to keep this address.]~n", + ok = io:format(Message, [XAString]), + case get_input() of + "" -> Current; + String -> parse_address(String) + end. + +external_address_prompt() -> + "~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". + +parse_address(String) -> + case inet:parse_address(String) of + {ok, Address} -> Address; + {error, einval} -> String + end. + +create_realm(ZompConf, Realm, ExAddress) -> + Current = + case lists:keyfind(external_port, 1, ZompConf) of + false -> 11311; + {external_port, none} -> 11311; + {external_port, P} -> P end, Message = "~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" - " The current public address is: ~ts. Press to keep this address.~n" - " DO NOT INCLUDE A PORT NUMBER IN THIS STEP~n", - ok = io:format(Message, [XAString]), - Input = get_input(), - ExAddress = - case - - - - {Host, Port} = - case proplists:get(external_address, 1, ZompConf) of - {external_address, ExAddress} -> - {external_port, ExPort} = lists:keyfind(external_port, 1, ZompConf), - {ExAddress, ExPort}; - false -> - prompt_prime(Realm) - end, - HostString = - Instructions = - "~n" - " Accept current config?~n" - " Host: ~ts Port: ~w~n", - ok = io:format(Instructions, [Host, Port]), - case string:trim(io:get_line("(^C to quit) [Y]/n: ")) of - "" -> create_realm(Realm, Prime); - "Y" -> create_realm(Realm, Prime); - "y" -> create_realm(Realm, Prime); - _ -> prompt_prime(Realm) - end. - -prompt_prime(Realm) -> - HostInstructions = - "~n" - " Enter the prime (permanent) server's publicly accessible hostname or " - "address. If prime is identified by an address and not a name, enter it " - "as either a normal IPv4 dot-notation or IPv6 colon-notation.~n" - " Note that address ranges will not be checked for validity.~n", - ok = io:format(HostInstructions), - HostString = get_input(), - Host = - case inet:parse_address(HostString) of - {ok, Address} -> Address; - {error, einval} -> HostString - end, - -create_prime(Realm, Host) -> - ok = io:format("~n Enter the local port number on which the host listen.~n"), - case prompt_port_number() of - {ok, ExPort} -> create_prime(Realm, Host, ExPort); - error -> create_prime(Realm, Host) - end. - - -create_realm(Realm, Host, InPort) -> - Message = "~n Enter the global port number on which the host will be available.~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), - case prompt_port_number() of - {ok, ExPort} -> create_prime(Realm, Host, ExPort, InPort); - error -> create_prime(Realm, Host, ExPort) - end. + ExPort = prompt_port_number(Current), + create_realm(ZompConf, Realm, ExAddress, ExPort). -prompt_port_number() -> +create_realm(ZompConf, Realm, ExAddress, ExPort) -> + Current = + case lists:keyfind(internal_port, 1, ZompConf) of + false -> 11311; + {internal_port, none} -> 11311; + {internal_port, P} -> P + end, + Message = + "~n" + " Enter the local (internal/LAN) port number at which this service should be " + "available. (This might be different from the public port visible from the internet" + "if you are port forwarding or have a complex network layout.)~n", + ok = io:format(Message), + InPort = prompt_port_number(Current), + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort). + +prompt_port_number(Current) -> Instructions = - " A port can be any number from 1 to 65535.~n" - " [Press enter to accept the default port: 11311]~n", - ok = io:format(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 get_input() of "" -> - {ok, 11311}; + Current; S -> try case list_to_integer(S) of Port when 16#ffff >= Port, Port > 0 -> - {ok, Port}; + Port; Illegal -> - Whoops = "~p is out of bounds (1~65535). Try again...", - error + 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]), - error + prompt_port_number(Current) end end. -create_realm(Realm, Prime) -> +create_realm(ZompConf, Realm, ExAddress, ExPort, InPort) -> Instructions = "~n" " Enter a username for the realm sysop.~n" @@ -1536,13 +1546,13 @@ create_realm(Realm, Prime) -> UserName = get_input(), case valid_lower0_9(UserName) of true -> - create_realm(Realm, Prime, UserName); + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName); false -> ok = io:format("Bad username ~tp. Try again.~n", [UserName]), - create_realm(Realm, Prime) + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort) end. -create_realm(Realm, Prime, UserName) -> +create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName) -> Instructions = "~n" " Enter an email address for the realm sysop.~n" @@ -1554,22 +1564,22 @@ create_realm(Realm, Prime, UserName) -> [User, Host] = string:lexemes(Email, "@"), case {valid_lower0_9(User), valid_label(Host)} of {true, true} -> - create_realm(Realm, Prime, UserName, Email); + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email); {false, true} -> Message = "The user part of the email address seems invalid. Try again.~n", ok = io:format(Message), - create_realm(Realm, Prime, UserName); + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName); {true, false} -> Message = "The host part of the email address seems invalid. Try again.~n", ok = io:format(Message), - create_realm(Realm, Prime, UserName); + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName); {false, false} -> Message = "This email address seems like its totally bonkers. Try again.~n", ok = io:format(Message), - create_realm(Realm, Prime, UserName) + create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName) end. -create_realm(Realm, Prime, UserName, Email) -> +create_realm(ZompConf, Realm, ExAddress, ExPort, InPort, UserName, Email) -> Instructions = "~n" " Enter the real name (or whatever name people recognize) for the sysop.~n" @@ -1604,6 +1614,21 @@ create_realm(Realm, Prime, UserName, Email) -> end, ok = lists:foreach(Copy, AllKeys), ok = lists:foreach(Drop, DangerousKeys), + Timestamp = calendar:now_to_universal_time(erlang:timestamp()), + {ok, RealmPubData} = file:read_file(RealmPub), + RealmPubRecord = + {{Realm, filename:basename(RealmPub)}, + realm, + {realm, Realm}, + crypto:hash(sha512, RealmPubData), + Timestamp}, + {ok, PackagePubData} = file:read_file(PackagePub), + PackagePubRecord = + {{Realm, filename:basename(PackagePub)}, + package, + {realm, Realm}, + crypto:hash(sha512, PackagePubData), + Timestamp}, Message = "~n" " All of the keys generated have been moved to the current directory.~n" @@ -1616,27 +1641,33 @@ create_realm(Realm, Prime, UserName, Email) -> " The package and sysop keys will need to be copied to the ~~/.zomp/keys/~s/~n" " directory on your personal or dev machine.~n", ok = io:format(Message, [Realm]), - Timestamp = calendar:now_to_universal_time(erlang:timestamp()), UserRecord = {{UserName, Realm}, [SysopPub], Email, RealName, 1, Timestamp}, RealmFile = filename:join(zomp_dir(), Realm ++ ".realm"), RealmMeta = [{realm, Realm}, {revision, 0}, - {prime, Prime}, + {prime, {ExAddress, ExPort}}, {private, []}, {mirrors, []}, {sysops, [UserRecord]}, - {realm_keys, [RealmPub]}, - {package_keys, [PackagePub]}], + {realm_keys, [RealmPubRecord]}, + {package_keys, [PackagePubRecord]}], + Realms = + case lists:keyfind(managed, 1, ZompConf) of + {managed, M} -> [Realm | M]; + false -> [Realm] + end, ZompFile = filename:join(zomp_dir(), "zomp.conf"), - ZompConf = - [{prime, Prime}, - {external_port, ExPort}, - {internal_port, InPort}], + Update = fun({K, V}, ZC) -> lists:keystore(K, 1, ZC, {K, V}) end, + NewConf = + [{managed, Realms}, + {external_address, ExAddress}, + {external_port, ExPort}, + {internal_port, InPort}], + NewZompConf = lists:foldl(Update, ZompConf, NewConf), ok = write_terms(RealmFile, RealmMeta), - ok = write_terms(ZompFile, ZompConf), - ok = log(info, "Wrote to ~ts:~n ~tp", [RealmFile, RealmMeta]), - ok = log(info, "Wrote to ~ts:~n ~tp", [ZompFile, ZompConf]), + ok = write_terms(ZompFile, NewZompConf), + ok = log(info, "Realm ~ts created.", [Realm]), halt(0).