akashcli 4.5.0__tar.gz → 4.5.2__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.
- {akashcli-4.5.0 → akashcli-4.5.2}/PKG-INFO +1 -1
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/relay.py +116 -17
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli.egg-info/PKG-INFO +1 -1
- {akashcli-4.5.0 → akashcli-4.5.2}/pyproject.toml +1 -1
- {akashcli-4.5.0 → akashcli-4.5.2}/README.md +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/__init__.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/auth.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/cli.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/client.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/config.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/crypto.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/env_manager.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/exceptions.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/exec.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/integrity.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/logger.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/models.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/p2p.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/packages.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/restore.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/snapshot_crypto.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/storage.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/terminal.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/uploader.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli/utils.py +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli.egg-info/SOURCES.txt +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli.egg-info/dependency_links.txt +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli.egg-info/entry_points.txt +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli.egg-info/requires.txt +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/akashcli.egg-info/top_level.txt +0 -0
- {akashcli-4.5.0 → akashcli-4.5.2}/setup.cfg +0 -0
|
@@ -554,32 +554,49 @@ 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
|
-
import httpx
|
|
559
561
|
import websockets.sync.client as _ws
|
|
560
562
|
|
|
561
|
-
# 1. Get a pairing code
|
|
563
|
+
# 1. Get a pairing code — try httpx first, fall back to urllib (system certs)
|
|
564
|
+
def _post_code(url: str) -> dict:
|
|
565
|
+
try:
|
|
566
|
+
import httpx as _hx
|
|
567
|
+
r = _hx.post(url, timeout=10)
|
|
568
|
+
r.raise_for_status()
|
|
569
|
+
return r.json()
|
|
570
|
+
except Exception:
|
|
571
|
+
pass
|
|
572
|
+
# urllib fallback — uses system CA bundle, works on Android/Termux
|
|
573
|
+
import urllib.request as _ur, urllib.error as _ue
|
|
574
|
+
req = _ur.Request(url, data=b"", method="POST")
|
|
575
|
+
with _ur.urlopen(req, timeout=10) as resp:
|
|
576
|
+
import json as _j
|
|
577
|
+
return _j.loads(resp.read())
|
|
578
|
+
|
|
562
579
|
try:
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
ttl = r.json().get("expires_in", 600)
|
|
580
|
+
data = _post_code(_guest_code_url(base_url))
|
|
581
|
+
code = data["code"]
|
|
582
|
+
ttl = data.get("expires_in", 600)
|
|
567
583
|
except Exception as e:
|
|
568
584
|
print(f" {_R}✗{_RS} could not get pairing code: {e}", file=sys.stderr)
|
|
585
|
+
print(f" {_DM}Make sure you can reach {base_url}{_RS}", file=sys.stderr)
|
|
569
586
|
return
|
|
570
587
|
|
|
571
588
|
print()
|
|
572
589
|
print(f" {_G}◉{_RS} guest relay ready")
|
|
573
590
|
print()
|
|
574
|
-
print(f"
|
|
591
|
+
print(f" {_DM}Pairing code:{_RS} {_C}{code}{_RS}")
|
|
575
592
|
print()
|
|
576
|
-
print(f" {_DM}On another machine
|
|
577
|
-
print(f" {_C}akash shell --code {code}{_RS}")
|
|
593
|
+
print(f" {_DM}On another machine:{_RS} {_C}akash shell --code {code}{_RS}")
|
|
578
594
|
print()
|
|
579
595
|
print(f" {_DM}Code expires in {ttl // 60} min · Ctrl-C to stop{_RS}")
|
|
596
|
+
print(f" {_DM}You will be asked to approve each connection before it gets shell access.{_RS}")
|
|
580
597
|
print()
|
|
581
598
|
|
|
582
|
-
# 2. Connect to host endpoint
|
|
599
|
+
# 2. Connect to host endpoint — handle knock/allow/deny then serve shell
|
|
583
600
|
while True:
|
|
584
601
|
try:
|
|
585
602
|
ws_url = _guest_host_url(base_url, code)
|
|
@@ -588,20 +605,70 @@ def guest_serve(base_url: str) -> None:
|
|
|
588
605
|
if ack.get("type") == "error":
|
|
589
606
|
print(f" {_R}✗{_RS} {ack.get('message')}", file=sys.stderr)
|
|
590
607
|
return
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
608
|
+
|
|
609
|
+
print(f" {_DM}waiting for connection…{_RS}", flush=True)
|
|
610
|
+
|
|
611
|
+
# Wait for either a knock (someone wants in) or a ping
|
|
612
|
+
while True:
|
|
613
|
+
try:
|
|
614
|
+
raw = ws.recv(timeout=35)
|
|
615
|
+
except Exception:
|
|
616
|
+
# Timeout or disconnect — break to reconnect loop
|
|
617
|
+
break
|
|
618
|
+
frame = json.loads(raw)
|
|
619
|
+
ftype = frame.get("type")
|
|
620
|
+
|
|
621
|
+
if ftype == "ping":
|
|
622
|
+
ws.send(json.dumps({"type": "pong"}))
|
|
623
|
+
continue
|
|
624
|
+
|
|
625
|
+
if ftype == "knock":
|
|
626
|
+
guest_ip = frame.get("ip", "unknown")
|
|
627
|
+
fp = frame.get("fingerprint", "????????")
|
|
628
|
+
print()
|
|
629
|
+
print(f" {_Y}⚡{_RS} Incoming connection request")
|
|
630
|
+
print(f" {_DM}From:{_RS} {_C}{guest_ip}{_RS}")
|
|
631
|
+
print(f" {_DM}Fingerprint:{_RS} {_C}{fp}{_RS}")
|
|
632
|
+
print()
|
|
633
|
+
|
|
634
|
+
# Prompt host — default is N (deny)
|
|
635
|
+
try:
|
|
636
|
+
sys.stdout.write(f" Allow? [y/N]: ")
|
|
637
|
+
sys.stdout.flush()
|
|
638
|
+
answer = sys.stdin.readline().strip().lower()
|
|
639
|
+
except (EOFError, KeyboardInterrupt):
|
|
640
|
+
answer = "n"
|
|
641
|
+
|
|
642
|
+
if answer == "y":
|
|
643
|
+
ws.send(json.dumps({"type": "allow"}))
|
|
644
|
+
print(f" {_G}✓{_RS} connection allowed — starting shell…")
|
|
645
|
+
print()
|
|
646
|
+
# Now serve shell frames
|
|
647
|
+
_run_relay_device(ws, f"guest:{code}")
|
|
648
|
+
print(f"\n {_DM}session ended — waiting for next connection…{_RS}\n")
|
|
649
|
+
else:
|
|
650
|
+
ws.send(json.dumps({"type": "deny"}))
|
|
651
|
+
print(f" {_R}✗{_RS} connection denied")
|
|
652
|
+
print()
|
|
653
|
+
# Loop back — still waiting on same code
|
|
654
|
+
continue
|
|
655
|
+
|
|
656
|
+
# Any other frame — ignore and keep waiting
|
|
657
|
+
continue
|
|
658
|
+
|
|
594
659
|
except KeyboardInterrupt:
|
|
595
660
|
print(f"\n {_DM}guest serve stopped{_RS}")
|
|
596
661
|
break
|
|
597
662
|
except Exception as exc:
|
|
598
|
-
print(f" {_Y}!{_RS} relay disconnected ({exc}) — reconnecting…")
|
|
663
|
+
print(f" {_Y}!{_RS} relay disconnected ({exc}) — reconnecting in 3s…")
|
|
599
664
|
time.sleep(3)
|
|
600
665
|
|
|
601
666
|
|
|
602
667
|
def guest_connect_shell(base_url: str, code: str) -> int:
|
|
603
668
|
"""
|
|
604
669
|
Connect to a guest host by pairing code — no API key needed.
|
|
670
|
+
Handles the approval handshake: shows a spinner while the host decides,
|
|
671
|
+
then opens a full PTY shell if approved.
|
|
605
672
|
Returns the remote exit code.
|
|
606
673
|
"""
|
|
607
674
|
import websockets.sync.client as _ws
|
|
@@ -636,10 +703,42 @@ def guest_connect_shell(base_url: str, code: str) -> int:
|
|
|
636
703
|
exit_code = 1
|
|
637
704
|
try:
|
|
638
705
|
with _ws.connect(ws_url, open_timeout=15, close_timeout=10) as ws:
|
|
639
|
-
|
|
640
|
-
|
|
706
|
+
# First frame: could be error, waiting_approval, or connected
|
|
707
|
+
first = json.loads(ws.recv())
|
|
708
|
+
ftype = first.get("type")
|
|
709
|
+
|
|
710
|
+
if ftype == "error":
|
|
641
711
|
_restore()
|
|
642
|
-
raise RuntimeError(
|
|
712
|
+
raise RuntimeError(first.get("message", "relay error"))
|
|
713
|
+
|
|
714
|
+
if ftype == "waiting_approval":
|
|
715
|
+
fp = first.get("fingerprint", "")
|
|
716
|
+
_restore() # restore terminal so we can print cleanly
|
|
717
|
+
print(f" {_Y}…{_RS} waiting for host to approve this connection…")
|
|
718
|
+
if fp:
|
|
719
|
+
print(f" {_DM}Your fingerprint: {_C}{fp}{_RS}")
|
|
720
|
+
print(f" {_DM}(host has 30 seconds to respond){_RS}")
|
|
721
|
+
print()
|
|
722
|
+
# Wait for next frame: connected or denied
|
|
723
|
+
try:
|
|
724
|
+
second = json.loads(ws.recv(timeout=35))
|
|
725
|
+
except Exception:
|
|
726
|
+
raise RuntimeError("Timed out waiting for host approval.")
|
|
727
|
+
if second.get("type") == "denied":
|
|
728
|
+
raise RuntimeError("Host declined the connection.")
|
|
729
|
+
if second.get("type") != "connected":
|
|
730
|
+
raise RuntimeError(second.get("message", "unexpected response from host"))
|
|
731
|
+
# Re-enter raw mode for the PTY session
|
|
732
|
+
try:
|
|
733
|
+
import tty as _tty2, termios as _term2
|
|
734
|
+
_old_settings = _term2.tcgetattr(_stdin_fd)
|
|
735
|
+
_tty2.setraw(_stdin_fd)
|
|
736
|
+
_raw_ok = True
|
|
737
|
+
except Exception:
|
|
738
|
+
pass
|
|
739
|
+
elif ftype != "connected":
|
|
740
|
+
_restore()
|
|
741
|
+
raise RuntimeError(first.get("message", "unexpected server response"))
|
|
643
742
|
|
|
644
743
|
ws.send(json.dumps({
|
|
645
744
|
"type": "pty_run",
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|