akashcli 4.5.0__tar.gz → 4.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {akashcli-4.5.0 → akashcli-4.5.1}/PKG-INFO +1 -1
  2. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/relay.py +96 -11
  3. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli.egg-info/PKG-INFO +1 -1
  4. {akashcli-4.5.0 → akashcli-4.5.1}/pyproject.toml +1 -1
  5. {akashcli-4.5.0 → akashcli-4.5.1}/README.md +0 -0
  6. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/__init__.py +0 -0
  7. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/auth.py +0 -0
  8. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/cli.py +0 -0
  9. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/client.py +0 -0
  10. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/config.py +0 -0
  11. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/crypto.py +0 -0
  12. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/env_manager.py +0 -0
  13. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/exceptions.py +0 -0
  14. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/exec.py +0 -0
  15. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/integrity.py +0 -0
  16. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/logger.py +0 -0
  17. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/models.py +0 -0
  18. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/p2p.py +0 -0
  19. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/packages.py +0 -0
  20. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/restore.py +0 -0
  21. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/snapshot_crypto.py +0 -0
  22. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/storage.py +0 -0
  23. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/terminal.py +0 -0
  24. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/uploader.py +0 -0
  25. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli/utils.py +0 -0
  26. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli.egg-info/SOURCES.txt +0 -0
  27. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli.egg-info/dependency_links.txt +0 -0
  28. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli.egg-info/entry_points.txt +0 -0
  29. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli.egg-info/requires.txt +0 -0
  30. {akashcli-4.5.0 → akashcli-4.5.1}/akashcli.egg-info/top_level.txt +0 -0
  31. {akashcli-4.5.0 → akashcli-4.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.5.0
3
+ Version: 4.5.1
4
4
  Summary: Akash CLI — secure remote shell relay, file vault, and device management
5
5
  License: MIT
6
6
  Keywords: akash,cli,relay,remote-shell,vault
@@ -554,6 +554,9 @@ def guest_serve(base_url: str) -> None:
554
554
  Host a shell session with no API key.
555
555
  Requests a pairing code from the server, prints it, then waits for
556
556
  someone to connect with: akash shell --code <CODE>
557
+
558
+ Security: every incoming connection shows the guest IP + fingerprint
559
+ and asks the host to approve before the shell is bridged.
557
560
  """
558
561
  import httpx
559
562
  import websockets.sync.client as _ws
@@ -571,15 +574,15 @@ def guest_serve(base_url: str) -> None:
571
574
  print()
572
575
  print(f" {_G}◉{_RS} guest relay ready")
573
576
  print()
574
- print(f" Your pairing code: {_C}{_Y}{code}{_RS}")
577
+ print(f" {_DM}Pairing code:{_RS} {_C}{code}{_RS}")
575
578
  print()
576
- print(f" {_DM}On another machine, run:{_RS}")
577
- print(f" {_C}akash shell --code {code}{_RS}")
579
+ print(f" {_DM}On another machine:{_RS} {_C}akash shell --code {code}{_RS}")
578
580
  print()
579
581
  print(f" {_DM}Code expires in {ttl // 60} min · Ctrl-C to stop{_RS}")
582
+ print(f" {_DM}You will be asked to approve each connection before it gets shell access.{_RS}")
580
583
  print()
581
584
 
582
- # 2. Connect to host endpoint and serve shell frames
585
+ # 2. Connect to host endpoint — handle knock/allow/deny then serve shell
583
586
  while True:
584
587
  try:
585
588
  ws_url = _guest_host_url(base_url, code)
@@ -588,20 +591,70 @@ def guest_serve(base_url: str) -> None:
588
591
  if ack.get("type") == "error":
589
592
  print(f" {_R}✗{_RS} {ack.get('message')}", file=sys.stderr)
590
593
  return
591
- print(f" {_DM}waiting for connection…{_RS}")
592
- _run_relay_device(ws, f"guest:{code}")
593
- print(f"\n {_DM}session ended — waiting for next connection…{_RS}\n")
594
+
595
+ print(f" {_DM}waiting for connection…{_RS}", flush=True)
596
+
597
+ # Wait for either a knock (someone wants in) or a ping
598
+ while True:
599
+ try:
600
+ raw = ws.recv(timeout=35)
601
+ except Exception:
602
+ # Timeout or disconnect — break to reconnect loop
603
+ break
604
+ frame = json.loads(raw)
605
+ ftype = frame.get("type")
606
+
607
+ if ftype == "ping":
608
+ ws.send(json.dumps({"type": "pong"}))
609
+ continue
610
+
611
+ if ftype == "knock":
612
+ guest_ip = frame.get("ip", "unknown")
613
+ fp = frame.get("fingerprint", "????????")
614
+ print()
615
+ print(f" {_Y}⚡{_RS} Incoming connection request")
616
+ print(f" {_DM}From:{_RS} {_C}{guest_ip}{_RS}")
617
+ print(f" {_DM}Fingerprint:{_RS} {_C}{fp}{_RS}")
618
+ print()
619
+
620
+ # Prompt host — default is N (deny)
621
+ try:
622
+ sys.stdout.write(f" Allow? [y/N]: ")
623
+ sys.stdout.flush()
624
+ answer = sys.stdin.readline().strip().lower()
625
+ except (EOFError, KeyboardInterrupt):
626
+ answer = "n"
627
+
628
+ if answer == "y":
629
+ ws.send(json.dumps({"type": "allow"}))
630
+ print(f" {_G}✓{_RS} connection allowed — starting shell…")
631
+ print()
632
+ # Now serve shell frames
633
+ _run_relay_device(ws, f"guest:{code}")
634
+ print(f"\n {_DM}session ended — waiting for next connection…{_RS}\n")
635
+ else:
636
+ ws.send(json.dumps({"type": "deny"}))
637
+ print(f" {_R}✗{_RS} connection denied")
638
+ print()
639
+ # Loop back — still waiting on same code
640
+ continue
641
+
642
+ # Any other frame — ignore and keep waiting
643
+ continue
644
+
594
645
  except KeyboardInterrupt:
595
646
  print(f"\n {_DM}guest serve stopped{_RS}")
596
647
  break
597
648
  except Exception as exc:
598
- print(f" {_Y}!{_RS} relay disconnected ({exc}) — reconnecting…")
649
+ print(f" {_Y}!{_RS} relay disconnected ({exc}) — reconnecting in 3s…")
599
650
  time.sleep(3)
600
651
 
601
652
 
602
653
  def guest_connect_shell(base_url: str, code: str) -> int:
603
654
  """
604
655
  Connect to a guest host by pairing code — no API key needed.
656
+ Handles the approval handshake: shows a spinner while the host decides,
657
+ then opens a full PTY shell if approved.
605
658
  Returns the remote exit code.
606
659
  """
607
660
  import websockets.sync.client as _ws
@@ -636,10 +689,42 @@ def guest_connect_shell(base_url: str, code: str) -> int:
636
689
  exit_code = 1
637
690
  try:
638
691
  with _ws.connect(ws_url, open_timeout=15, close_timeout=10) as ws:
639
- ack = json.loads(ws.recv())
640
- if ack.get("type") == "error":
692
+ # First frame: could be error, waiting_approval, or connected
693
+ first = json.loads(ws.recv())
694
+ ftype = first.get("type")
695
+
696
+ if ftype == "error":
641
697
  _restore()
642
- raise RuntimeError(ack.get("message", "relay error"))
698
+ raise RuntimeError(first.get("message", "relay error"))
699
+
700
+ if ftype == "waiting_approval":
701
+ fp = first.get("fingerprint", "")
702
+ _restore() # restore terminal so we can print cleanly
703
+ print(f" {_Y}…{_RS} waiting for host to approve this connection…")
704
+ if fp:
705
+ print(f" {_DM}Your fingerprint: {_C}{fp}{_RS}")
706
+ print(f" {_DM}(host has 30 seconds to respond){_RS}")
707
+ print()
708
+ # Wait for next frame: connected or denied
709
+ try:
710
+ second = json.loads(ws.recv(timeout=35))
711
+ except Exception:
712
+ raise RuntimeError("Timed out waiting for host approval.")
713
+ if second.get("type") == "denied":
714
+ raise RuntimeError("Host declined the connection.")
715
+ if second.get("type") != "connected":
716
+ raise RuntimeError(second.get("message", "unexpected response from host"))
717
+ # Re-enter raw mode for the PTY session
718
+ try:
719
+ import tty as _tty2, termios as _term2
720
+ _old_settings = _term2.tcgetattr(_stdin_fd)
721
+ _tty2.setraw(_stdin_fd)
722
+ _raw_ok = True
723
+ except Exception:
724
+ pass
725
+ elif ftype != "connected":
726
+ _restore()
727
+ raise RuntimeError(first.get("message", "unexpected server response"))
643
728
 
644
729
  ws.send(json.dumps({
645
730
  "type": "pty_run",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 4.5.0
3
+ Version: 4.5.1
4
4
  Summary: Akash CLI — secure remote shell relay, file vault, and device management
5
5
  License: MIT
6
6
  Keywords: akash,cli,relay,remote-shell,vault
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "akashcli"
3
- version = "4.5.0"
3
+ version = "4.5.1"
4
4
  description = "Akash CLI — secure remote shell relay, file vault, and device management"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes