diff --git a/cli/commands/ui.py b/cli/commands/ui.py index aee2d21d08..6eaeeb793e 100644 --- a/cli/commands/ui.py +++ b/cli/commands/ui.py @@ -15,7 +15,9 @@ def ui( from studio.backend.run import run_server if not silent: - typer.echo(f"Starting Unsloth UI on http://{host}:{port}") + from studio.backend.run import _resolve_external_ip + display_host = _resolve_external_ip() if host == "0.0.0.0" else host + typer.echo(f"Starting Unsloth Studio on http://{display_host}:{port}") run_server( host=host, diff --git a/studio/backend/run.py b/studio/backend/run.py index 8945d438bb..3567401feb 100644 --- a/studio/backend/run.py +++ b/studio/backend/run.py @@ -11,6 +11,51 @@ if str(backend_dir) not in sys.path: sys.path.insert(0, str(backend_dir)) +def _resolve_external_ip() -> str: + """ + Resolve the machine's external IP address. + + Tries (in order): + 1. GCE metadata server (instant, works on Google Cloud VMs) + 2. ifconfig.me (works anywhere with internet) + 3. LAN IP via UDP socket trick (fallback) + """ + import urllib.request + import socket + + # 1. Try GCE metadata server (responds in <10ms on GCE, times out fast elsewhere) + try: + req = urllib.request.Request( + "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip", + headers={"Metadata-Flavor": "Google"}, + ) + with urllib.request.urlopen(req, timeout=1) as resp: + ip = resp.read().decode().strip() + if ip: + return ip + except Exception: + pass + + # 2. Try public IP service + try: + with urllib.request.urlopen("https://ifconfig.me", timeout=3) as resp: + ip = resp.read().decode().strip() + if ip: + return ip + except Exception: + pass + + # 3. Fallback: LAN IP via UDP socket trick + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + except Exception: + return "0.0.0.0" + + def run_server( host: str = "0.0.0.0", port: int = 8000, @@ -57,11 +102,15 @@ def run_server( time.sleep(3) if not silent: + display_host = _resolve_external_ip() if host == "0.0.0.0" else host + print("") print("=" * 50) - print(f"🦥 Unsloth UI Backend is running on port {port}") - print(f" API: http://{host}:{port}/api") - print(f" Health: http://{host}:{port}/api/health") + print(f"🦥 Unsloth Studio is running on port {port}") + print(f" Local: http://localhost:{port}") + print(f" External: http://{display_host}:{port}") + print(f" API: http://{display_host}:{port}/api") + print(f" Health: http://{display_host}:{port}/api/health") print("=" * 50) return app