|
6 | 6 | from .hf import has_transformers, has_datasets, load_model_and_tokenizer, export_torchscript, export_onnx |
7 | 7 | from .p2p import generate_join_link, parse_join_link |
8 | 8 | from .p2p_runtime import run_p2p_node, P2PNode |
| 9 | +from .nat import auto_port_forward, get_public_ip |
9 | 10 |
|
10 | 11 | console = Console() |
11 | 12 |
|
@@ -183,6 +184,135 @@ def api(host, port, p2p_port, bootstrap): |
183 | 184 | console.print(f"[bold green]🚀 Starting Main Point API on http://{host}:{port}[/bold green]") |
184 | 185 | uvicorn.run("bee2bee.api:app", host=host, port=port, reload=False) |
185 | 186 |
|
| 187 | +@cli.command() |
| 188 | +@click.option('--port', default=4003, help='Port to forward') |
| 189 | +@click.option('--test/--no-test', default=True, help='Test connection after forwarding') |
| 190 | +def auto_forward(port, test): |
| 191 | + """Automatically forward a port with UPnP and fallbacks""" |
| 192 | + async def _run(): |
| 193 | + console.print(f"[dim]Starting auto port forwarding for port {port}...[/dim]") |
| 194 | + |
| 195 | + # Try auto forwarding |
| 196 | + result = await auto_port_forward(port, "TCP") |
| 197 | + |
| 198 | + if result.success: |
| 199 | + if result.method == "UPnP": |
| 200 | + console.print(f"[green]✅ UPnP Port Forwarding SUCCESS![/green]") |
| 201 | + else: |
| 202 | + console.print(f"[green]✅ {result.method} SUCCESS! (Fallback)[/green]") |
| 203 | + |
| 204 | + console.print(f" External: {result.external_ip}:{result.external_port}") |
| 205 | + console.print(f" Method: {result.method}") |
| 206 | + console.print(f" Details: {result.details}") |
| 207 | + |
| 208 | + if result.fallback_used: |
| 209 | + console.print(f"[yellow]⚠️ Using fallback method - manual forwarding may still be needed[/yellow]") |
| 210 | + |
| 211 | + # Update config if this is the default P2P port |
| 212 | + if port == 4003: |
| 213 | + from .config import set_bootstrap_url |
| 214 | + set_bootstrap_url(f"ws://{result.external_ip}:{result.external_port}") |
| 215 | + console.print(f"[green]✓ Updated bootstrap_url in config[/green]") |
| 216 | + |
| 217 | + if test: |
| 218 | + console.print(f"\n[cyan]Testing connection...[/cyan]") |
| 219 | + success = await test_connection(f"{result.external_ip}:{result.external_port}") |
| 220 | + if success: |
| 221 | + console.print("[green]✅ Connection test successful![/green]") |
| 222 | + else: |
| 223 | + console.print("[yellow]⚠️ Connection test failed (firewall may be blocking)[/yellow]") |
| 224 | + |
| 225 | + else: |
| 226 | + console.print(f"[red]❌ All auto methods failed[/red]") |
| 227 | + |
| 228 | + if result.external_ip: |
| 229 | + console.print(f"[yellow]Your public IP is: {result.external_ip}[/yellow]") |
| 230 | + console.print(f"[yellow]Manual port forwarding required:[/yellow]") |
| 231 | + console.print(f" 1. Go to router admin (192.168.1.1)") |
| 232 | + console.print(f" 2. Forward TCP port {port}") |
| 233 | + console.print(f" 3. Use: ws://{result.external_ip}:{port}") |
| 234 | + |
| 235 | + asyncio.run(_run()) |
| 236 | + |
| 237 | + |
| 238 | +@cli.command() |
| 239 | +@click.option('--port', default=4003, help='Port to check') |
| 240 | +def port_status(port): |
| 241 | + """Check port forwarding status""" |
| 242 | + async def _run(): |
| 243 | + from .utils import get_lan_ip |
| 244 | + |
| 245 | + console.print(f"[dim]Checking port {port} status...[/dim]") |
| 246 | + |
| 247 | + # Get network info |
| 248 | + lan_ip = get_lan_ip() |
| 249 | + |
| 250 | + console.print(f"\n[cyan]Network Information:[/cyan]") |
| 251 | + console.print(f" Local IP: {lan_ip}") |
| 252 | + console.print(f" Port: {port}") |
| 253 | + |
| 254 | + # Try auto forwarding |
| 255 | + result = await auto_port_forward(port, "TCP") |
| 256 | + |
| 257 | + console.print(f"\n[cyan]Port Forwarding Status:[/cyan]") |
| 258 | + if result.success: |
| 259 | + if result.method == "UPnP": |
| 260 | + status = "[green]✅ AUTO-FORWARDED (UPnP)[/green]" |
| 261 | + else: |
| 262 | + status = f"[yellow]⚠️ DETECTED ({result.method})[/yellow]" |
| 263 | + |
| 264 | + console.print(f" Status: {status}") |
| 265 | + console.print(f" External IP: {result.external_ip}") |
| 266 | + console.print(f" External Port: {result.external_port}") |
| 267 | + console.print(f" Details: {result.details}") |
| 268 | + |
| 269 | + if result.fallback_used: |
| 270 | + console.print(f" [dim](Using fallback method)[/dim]") |
| 271 | + |
| 272 | + else: |
| 273 | + console.print(f" Status: [red]❌ NOT FORWARDED[/red]") |
| 274 | + |
| 275 | + if result.external_ip: |
| 276 | + console.print(f" Your Public IP: {result.external_ip}") |
| 277 | + console.print(f" [yellow]Manual forwarding required[/yellow]") |
| 278 | + |
| 279 | + # Check local port |
| 280 | + console.print(f"\n[cyan]Local Port Check:[/cyan]") |
| 281 | + if is_port_open_locally(port): |
| 282 | + console.print(f" [green]✓ Port {port} is open locally[/green]") |
| 283 | + else: |
| 284 | + console.print(f" [red]✗ Port {port} is not open locally[/red]") |
| 285 | + |
| 286 | + asyncio.run(_run()) |
| 287 | + |
| 288 | + |
| 289 | +async def test_connection(addr: str) -> bool: |
| 290 | + """Test if address is accessible""" |
| 291 | + import websockets |
| 292 | + try: |
| 293 | + # Add ws:// if not present |
| 294 | + if not addr.startswith("ws://"): |
| 295 | + addr = f"ws://{addr}" |
| 296 | + |
| 297 | + async with websockets.connect(addr, timeout=5) as ws: |
| 298 | + await ws.close() |
| 299 | + return True |
| 300 | + except Exception: |
| 301 | + return False |
| 302 | + |
| 303 | + |
| 304 | +def is_port_open_locally(port: int) -> bool: |
| 305 | + """Check if port is open locally""" |
| 306 | + import socket |
| 307 | + try: |
| 308 | + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 309 | + sock.settimeout(1) |
| 310 | + result = sock.connect_ex(('127.0.0.1', port)) |
| 311 | + sock.close() |
| 312 | + return result == 0 |
| 313 | + except: |
| 314 | + return False |
| 315 | + |
186 | 316 |
|
187 | 317 |
|
188 | 318 | if __name__ == "__main__": |
|
0 commit comments