mirror of
https://github.com/markqvist/rnsh.git
synced 2025-07-22 06:38:58 -04:00
Remove service name from aspects #12; minor tweaks
- Remove service name from RNS destination aspects. Service name now selects a suffix for the identity file and should only be supplied on the listener. The initiator only needs the destination hash of the listener to connect. - Show a spinner during link establishment on tty sessions - Attempt to catch and beautify exceptions on initiator
This commit is contained in:
parent
bd12efd7cf
commit
a07ce53bf9
7 changed files with 199 additions and 110 deletions
25
rnsh/args.py
25
rnsh/args.py
|
@ -18,20 +18,20 @@ def _split_array_at(arr: [_T], at: _T) -> ([_T], [_T]):
|
|||
usage = \
|
||||
'''
|
||||
Usage:
|
||||
rnsh [--config <configdir>] [-i <identityfile>] [-s <service_name>] [-l] -p
|
||||
rnsh -l [--config <configfile>] [-i <identityfile>] [-s <service_name>]
|
||||
[-v... | -q...] [-b <period>] (-n | -a <identity_hash> [-a <identity_hash>] ...)
|
||||
[-A | -C] [[--] <program> [<arg> ...]]
|
||||
rnsh [--config <configfile>] [-i <identityfile>] [-s <service_name>]
|
||||
[-v... | -q...] [-N] [-m] [-w <timeout>] <destination_hash>
|
||||
[[--] <program> [<arg> ...]]
|
||||
rnsh -l [-c <configdir>] [-i <identityfile> | -s <service_name>] [-v... | -q...] -p
|
||||
rnsh -l [-c <configdir>] [-i <identityfile> | -s <service_name>] [-v... | -q...]
|
||||
[-b <period>] (-n | -a <identity_hash> [-a <identity_hash>] ...) [-A | -C]
|
||||
[[--] <program> [<arg> ...]]
|
||||
rnsh [-c <configdir>] [-i <identityfile>] [-v... | -q...] -p
|
||||
rnsh [-c <configdir>] [-i <identityfile>] [-v... | -q...] [-N] [-m] [-w <timeout>]
|
||||
<destination_hash> [[--] <program> [<arg> ...]]
|
||||
rnsh -h
|
||||
rnsh --version
|
||||
|
||||
Options:
|
||||
--config DIR Alternate Reticulum config directory to use
|
||||
-c DIR --config DIR Alternate Reticulum config directory to use
|
||||
-i FILE --identity FILE Specific identity file to use
|
||||
-s NAME --service NAME Listen on/connect to specific service name if not default
|
||||
-s NAME --service NAME Service name for identity file if not default
|
||||
-p --print-identity Print identity information and exit
|
||||
-l --listen Listen (server) mode. If supplied, <program> <arg>...will
|
||||
be used as the command line when the initiator does not
|
||||
|
@ -59,6 +59,7 @@ Options:
|
|||
-h --help Show this help
|
||||
'''
|
||||
|
||||
DEFAULT_SERVICE_NAME = "default"
|
||||
|
||||
class Args:
|
||||
def __init__(self, argv: [str]):
|
||||
|
@ -75,9 +76,11 @@ class Args:
|
|||
|
||||
args = docopt.docopt(usage, argv=self.docopts_argv[1:], version=f"rnsh {rnsh.__version__}")
|
||||
# json.dump(args, sys.stdout)
|
||||
|
||||
self.service_name = args.get("--service", None) or "default"
|
||||
|
||||
self.listen = args.get("--listen", None) or False
|
||||
self.service_name = args.get("--service", None)
|
||||
if self.listen and (self.service_name is None or len(self.service_name) > 0):
|
||||
self.service_name = DEFAULT_SERVICE_NAME
|
||||
self.identity = args.get("--identity", None)
|
||||
self.config = args.get("--config", None)
|
||||
self.print_identity = args.get("--print-identity", None) or False
|
||||
|
|
113
rnsh/rnsh.py
113
rnsh/rnsh.py
|
@ -93,11 +93,17 @@ def _sigint_handler(sig, frame):
|
|||
signal.signal(signal.SIGINT, _sigint_handler)
|
||||
|
||||
|
||||
def _prepare_identity(identity_path):
|
||||
def _sanitize_service_name(service_name:str) -> str:
|
||||
return re.sub(r'\W+', '', service_name)
|
||||
|
||||
|
||||
def _prepare_identity(identity_path, service_name: str = None):
|
||||
global _identity
|
||||
log = _get_logger("_prepare_identity")
|
||||
service_name = _sanitize_service_name(service_name or "")
|
||||
if identity_path is None:
|
||||
identity_path = RNS.Reticulum.identitypath + "/" + APP_NAME
|
||||
identity_path = RNS.Reticulum.identitypath + "/" + APP_NAME + \
|
||||
(f".{service_name}" if service_name and len(service_name) > 0 else "")
|
||||
|
||||
if os.path.isfile(identity_path):
|
||||
_identity = RNS.Identity.from_file(identity_path)
|
||||
|
@ -111,26 +117,32 @@ def _prepare_identity(identity_path):
|
|||
def _print_identity(configdir, identitypath, service_name, include_destination: bool):
|
||||
global _reticulum
|
||||
_reticulum = RNS.Reticulum(configdir=configdir, loglevel=RNS.LOG_INFO)
|
||||
_prepare_identity(identitypath)
|
||||
destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, service_name)
|
||||
if service_name and len(service_name) > 0:
|
||||
print(f"Using service name \"{service_name}\"")
|
||||
_prepare_identity(identitypath, service_name)
|
||||
destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME)
|
||||
print("Identity : " + str(_identity))
|
||||
if include_destination:
|
||||
print("Listening on : " + RNS.prettyhexrep(destination.hash))
|
||||
exit(0)
|
||||
|
||||
|
||||
async def _listen(configdir, command, identitypath=None, service_name="default", verbosity=0, quietness=0, allowed=None,
|
||||
async def _listen(configdir, command, identitypath=None, service_name=None, verbosity=0, quietness=0, allowed=None,
|
||||
disable_auth=None, announce_period=900, no_remote_command=True, remote_cmd_as_args=False):
|
||||
global _identity, _allow_all, _allowed_identity_hashes, _reticulum, _cmd, _destination, _no_remote_command
|
||||
global _remote_cmd_as_args
|
||||
log = _get_logger("_listen")
|
||||
if service_name is None or len(service_name) == 0:
|
||||
service_name = "default"
|
||||
|
||||
log.info(f"Using service name {service_name}")
|
||||
|
||||
|
||||
targetloglevel = RNS.LOG_INFO + verbosity - quietness
|
||||
_reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
|
||||
rnslogging.RnsHandler.set_log_level_with_rns_level(targetloglevel)
|
||||
_prepare_identity(identitypath)
|
||||
_destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, service_name)
|
||||
_prepare_identity(identitypath, service_name)
|
||||
_destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME)
|
||||
|
||||
_cmd = command
|
||||
if _cmd is None or len(_cmd) == 0:
|
||||
|
@ -218,12 +230,33 @@ async def _listen(configdir, command, identitypath=None, service_name="default",
|
|||
await asyncio.sleep(0.01)
|
||||
|
||||
|
||||
async def _spin(until: callable = None, timeout: float | None = None) -> bool:
|
||||
async def _spin_tty(until=None, msg=None, timeout=None):
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
if timeout != None:
|
||||
timeout = time.time()+timeout
|
||||
|
||||
print(msg+" ", end=" ")
|
||||
while (timeout == None or time.time()<timeout) and not until():
|
||||
await asyncio.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
print("\r"+" "*len(msg)+" \r", end="")
|
||||
|
||||
if timeout != None and time.time() > timeout:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
async def _spin_pipe(until: callable = None, msg=None, timeout: float | None = None) -> bool:
|
||||
if timeout is not None:
|
||||
timeout += time.time()
|
||||
|
||||
while (timeout is None or time.time() < timeout) and not until():
|
||||
if await _check_finished(0.01):
|
||||
if await _check_finished(0.1):
|
||||
raise asyncio.CancelledError()
|
||||
if timeout is not None and time.time() > timeout:
|
||||
return False
|
||||
|
@ -231,6 +264,13 @@ async def _spin(until: callable = None, timeout: float | None = None) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
async def _spin(until: callable = None, msg=None, timeout: float | None = None) -> bool:
|
||||
if os.isatty(1):
|
||||
return await _spin_tty(until, msg, timeout)
|
||||
else:
|
||||
return await _spin_pipe(until, msg, timeout)
|
||||
|
||||
|
||||
_link: RNS.Link | None = None
|
||||
_remote_exec_grace = 2.0
|
||||
_new_data: asyncio.Event | None = None
|
||||
|
@ -266,7 +306,7 @@ class RemoteExecutionError(Exception):
|
|||
|
||||
|
||||
async def _initiate_link(configdir, identitypath=None, verbosity=0, quietness=0, noid=False, destination=None,
|
||||
service_name="default", timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
global _identity, _reticulum, _link, _destination, _remote_exec_grace, _tr, _new_data
|
||||
log = _get_logger("_initiate_link")
|
||||
|
||||
|
@ -291,7 +331,8 @@ async def _initiate_link(configdir, identitypath=None, verbosity=0, quietness=0,
|
|||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
log.info(f"Requesting path...")
|
||||
if not await _spin(until=lambda: RNS.Transport.has_path(destination_hash), timeout=timeout):
|
||||
if not await _spin(until=lambda: RNS.Transport.has_path(destination_hash), msg="Requesting path...",
|
||||
timeout=timeout):
|
||||
raise RemoteExecutionError("Path not found")
|
||||
|
||||
if _destination is None:
|
||||
|
@ -300,8 +341,7 @@ async def _initiate_link(configdir, identitypath=None, verbosity=0, quietness=0,
|
|||
listener_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
service_name
|
||||
APP_NAME
|
||||
)
|
||||
|
||||
if _link is None or _link.status == RNS.Link.PENDING:
|
||||
|
@ -312,7 +352,8 @@ async def _initiate_link(configdir, identitypath=None, verbosity=0, quietness=0,
|
|||
_link.set_link_closed_callback(_client_link_closed)
|
||||
|
||||
log.info(f"Establishing link...")
|
||||
if not await _spin(until=lambda: _link.status == RNS.Link.ACTIVE, timeout=timeout):
|
||||
if not await _spin(until=lambda: _link.status == RNS.Link.ACTIVE, msg="Establishing link...",
|
||||
timeout=timeout):
|
||||
raise RemoteExecutionError("Could not establish link with " + RNS.prettyhexrep(destination_hash))
|
||||
|
||||
log.debug("Have link")
|
||||
|
@ -323,8 +364,16 @@ async def _initiate_link(configdir, identitypath=None, verbosity=0, quietness=0,
|
|||
_link.set_packet_callback(_client_packet_handler)
|
||||
|
||||
|
||||
async def _handle_error(errmsg: protocol.Message):
|
||||
if isinstance(errmsg, protocol.ErrorMessage):
|
||||
with contextlib.suppress(Exception):
|
||||
if _link and _link.status == RNS.Link.ACTIVE:
|
||||
_link.teardown()
|
||||
await asyncio.sleep(0.1)
|
||||
raise RemoteExecutionError(f"Remote error: {errmsg.msg}")
|
||||
|
||||
async def _initiate(configdir: str, identitypath: str, verbosity: int, quietness: int, noid: bool, destination: str,
|
||||
service_name: str, timeout: float, command: [str] | None = None):
|
||||
timeout: float, command: [str] | None = None):
|
||||
global _new_data, _finished, _tr, _cmd, _pre_input
|
||||
log = _get_logger("_initiate")
|
||||
loop = asyncio.get_running_loop()
|
||||
|
@ -339,7 +388,6 @@ async def _initiate(configdir: str, identitypath: str, verbosity: int, quietness
|
|||
quietness=quietness,
|
||||
noid=noid,
|
||||
destination=destination,
|
||||
service_name=service_name,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
|
@ -360,6 +408,7 @@ async def _initiate(configdir: str, identitypath: str, verbosity: int, quietness
|
|||
try:
|
||||
vp = _pq.get(timeout=max(outlet.rtt * 20, 5))
|
||||
vm = messenger.receive(vp)
|
||||
await _handle_error(vm)
|
||||
if not isinstance(vm, protocol.VersionInfoMessage):
|
||||
raise Exception("Invalid message received")
|
||||
log.debug(f"Server version info: sw {vm.sw_version} prot {vm.protocol_version}")
|
||||
|
@ -433,6 +482,7 @@ async def _initiate(configdir: str, identitypath: str, verbosity: int, quietness
|
|||
try:
|
||||
packet = _pq.get(timeout=sleeper.next_sleep_time() if not processed else 0.0005)
|
||||
message = messenger.receive(packet)
|
||||
await _handle_error(message)
|
||||
processed = True
|
||||
if isinstance(message, protocol.StreamDataMessage):
|
||||
if message.stream_id == protocol.StreamDataMessage.STREAM_ID_STDOUT:
|
||||
|
@ -534,22 +584,25 @@ async def _rnsh_cli_main():
|
|||
remote_cmd_as_args=args.remote_cmd_as_args)
|
||||
return 0
|
||||
|
||||
if args.destination is not None and args.service_name is not None:
|
||||
return_code = await _initiate(
|
||||
configdir=args.config,
|
||||
identitypath=args.identity,
|
||||
verbosity=args.verbose,
|
||||
quietness=args.quiet,
|
||||
noid=args.no_id,
|
||||
destination=args.destination,
|
||||
service_name=args.service_name,
|
||||
timeout=args.timeout,
|
||||
command=args.command_line
|
||||
)
|
||||
return return_code if args.mirror else 0
|
||||
if args.destination is not None:
|
||||
try:
|
||||
return_code = await _initiate(
|
||||
configdir=args.config,
|
||||
identitypath=args.identity,
|
||||
verbosity=args.verbose,
|
||||
quietness=args.quiet,
|
||||
noid=args.no_id,
|
||||
destination=args.destination,
|
||||
timeout=args.timeout,
|
||||
command=args.command_line
|
||||
)
|
||||
return return_code if args.mirror else 0
|
||||
except Exception as ex:
|
||||
print(f"{ex}")
|
||||
return 255;
|
||||
else:
|
||||
print("")
|
||||
print(args.usage)
|
||||
print(rnsh.args.usage)
|
||||
print("")
|
||||
return 1
|
||||
|
||||
|
|
|
@ -354,7 +354,7 @@ class ListenerSession:
|
|||
message = self.messenger.receive(raw)
|
||||
self._handle_message(message)
|
||||
except Exception as ex:
|
||||
self._protocol_error("unusable packet")
|
||||
self._protocol_error(f"error receiving packet: {ex}")
|
||||
|
||||
|
||||
class RNSOutlet(LSOutletBase):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue