unitlab 2.3.17__tar.gz → 2.3.20__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unitlab
3
- Version: 2.3.17
3
+ Version: 2.3.20
4
4
  Home-page: https://github.com/teamunitlab/unitlab-sdk
5
5
  Author: Unitlab Inc.
6
6
  Author-email: team@unitlab.ai
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
2
2
 
3
3
  setup(
4
4
  name="unitlab",
5
- version="2.3.17",
5
+ version="2.3.20",
6
6
  license="MIT",
7
7
  author="Unitlab Inc.",
8
8
  author_email="team@unitlab.ai",
@@ -206,7 +206,7 @@ class CloudflareAPITunnel:
206
206
  print(" Assuming routes are configured in dashboard.")
207
207
  return True
208
208
 
209
- def create_device_tunnel(self):
209
+ def create_device_tunnel(self, retry_on_fail=True):
210
210
  """
211
211
  Create a unique tunnel for this device if it doesn't exist
212
212
  """
@@ -372,11 +372,26 @@ class CloudflareAPITunnel:
372
372
  self.create_dns_records()
373
373
  self.update_tunnel_config()
374
374
 
375
+ # Check if we need to recreate the tunnel
376
+ if device_tunnel and device_tunnel.get('needs_recreation'):
377
+ print("🔄 Recreating tunnel due to access issues...")
378
+ # Delete the old tunnel
379
+ tunnel_id = device_tunnel['id']
380
+ delete_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{tunnel_id}"
381
+ delete_response = requests.delete(delete_url, headers=self.headers)
382
+ if delete_response.status_code in [200, 204, 404]:
383
+ print(f" ✅ Deleted old tunnel")
384
+ # Try creating a new one with a timestamp suffix
385
+ self.clean_device_id = f"{self.device_id.replace(' ', '').replace('-', '').replace('.', '').replace('_', '')[:24]}-{int(time.time())}"
386
+ device_tunnel = self.create_device_tunnel(retry_on_fail=False)
387
+ if device_tunnel:
388
+ print(f" ✅ Created new tunnel")
389
+
375
390
  if not device_tunnel:
376
391
  print("❌ Could not create/find device tunnel")
377
392
  # Fallback to shared tunnel if API fails
378
393
  print("⚠️ Falling back to shared tunnel...")
379
- service_token = "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakLTazBaalF0WXpjek1tSm1ZVGt4WlRRMCJ9"
394
+ service_token = "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakLTazBmalF0WXpjek1tSm1ZVGt4WlRRMCJ9"
380
395
  cmd = [
381
396
  cloudflared_path,
382
397
  "tunnel",
@@ -402,12 +417,22 @@ class CloudflareAPITunnel:
402
417
  self._save_tunnel_credentials(device_tunnel)
403
418
  # Continue to use credentials below
404
419
  else:
405
- print("⚠️ No stored credentials, using tunnel token...")
420
+ print("⚠️ No stored credentials, requesting tunnel token...")
406
421
  # Get token for this tunnel
407
422
  token_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{tunnel_id}/token"
423
+ print(f" Token URL: {token_url}")
424
+
408
425
  token_response = requests.get(token_url, headers=self.headers)
426
+ print(f" Token response status: {token_response.status_code}")
427
+
409
428
  if token_response.status_code == 200:
410
- token = token_response.json()['result']
429
+ result = token_response.json()
430
+ if 'result' in result:
431
+ token = result['result']
432
+ print(f" ✅ Got tunnel token")
433
+ else:
434
+ print(f" ❌ No token in response: {result}")
435
+ return None
411
436
 
412
437
  # Check if config file exists with ingress rules
413
438
  if config_file.exists():
@@ -432,8 +457,21 @@ class CloudflareAPITunnel:
432
457
  "--token", token
433
458
  ]
434
459
  else:
435
- print("❌ Could not get tunnel token")
436
- return None
460
+ print(f"❌ Could not get tunnel token: {token_response.status_code}")
461
+ if token_response.text:
462
+ print(f" Error: {token_response.text}")
463
+
464
+ # If 404, tunnel doesn't exist or we don't have access
465
+ if token_response.status_code == 404:
466
+ print(f"🔄 Tunnel not accessible (404), will recreate...")
467
+ # Mark for recreation and continue
468
+ device_tunnel['needs_recreation'] = True
469
+ # Set cmd to None to skip the rest of this block
470
+ cmd = None
471
+ else:
472
+ print(f" ℹ️ Token endpoint returned {token_response.status_code}")
473
+ print(f" This might mean the tunnel needs to be recreated manually")
474
+ return None
437
475
 
438
476
  # Only use credentials if we haven't already set cmd with token
439
477
  if cmd is None and creds_file.exists():
@@ -458,27 +496,37 @@ class CloudflareAPITunnel:
458
496
  tunnel_id
459
497
  ]
460
498
 
461
- self.tunnel_process = subprocess.Popen(
462
- cmd,
463
- stdout=subprocess.PIPE,
464
- stderr=subprocess.STDOUT,
465
- text=True,
466
- bufsize=1
467
- )
468
-
469
- print("⏳ Waiting for tunnel to connect...")
470
- time.sleep(5)
471
-
472
- if self.tunnel_process.poll() is None:
473
- print("✅ Tunnel is running!")
474
- print(f"📌 Device ID: {self.clean_device_id}")
475
- print(f"📌 Jupyter URL: {self.jupyter_url}")
476
- print(f"📌 SSH hostname: {self.ssh_hostname}")
477
- print(f"📌 SSH command: ssh -o ProxyCommand='cloudflared access ssh --hostname {self.ssh_hostname}' user@localhost")
478
- return self.tunnel_process
499
+ # Only start the tunnel if we have a valid command
500
+ if cmd:
501
+ self.tunnel_process = subprocess.Popen(
502
+ cmd,
503
+ stdout=subprocess.PIPE,
504
+ stderr=subprocess.STDOUT,
505
+ text=True,
506
+ bufsize=1
507
+ )
508
+
509
+ print("⏳ Waiting for tunnel to connect...")
510
+ time.sleep(5)
511
+
512
+ if self.tunnel_process.poll() is None:
513
+ print(" Tunnel is running!")
514
+ print(f"📌 Device ID: {self.clean_device_id}")
515
+ print(f"📌 Jupyter URL: {self.jupyter_url}")
516
+ print(f"📌 SSH hostname: {self.ssh_hostname}")
517
+ print(f"📌 SSH command: ssh -o ProxyCommand='cloudflared access ssh --hostname {self.ssh_hostname}' user@localhost")
518
+ return self.tunnel_process
519
+ else:
520
+ output = self.tunnel_process.stdout.read() if self.tunnel_process.stdout else ""
521
+ print(f"❌ Tunnel failed to start: {output}")
522
+ return None
479
523
  else:
480
- output = self.tunnel_process.stdout.read() if self.tunnel_process.stdout else ""
481
- print(f"❌ Tunnel failed to start: {output}")
524
+ # If no cmd, it means we need to handle recreation or other error
525
+ if device_tunnel and device_tunnel.get('needs_recreation'):
526
+ # The recreation happens above before this point
527
+ print("❌ Failed to start tunnel after recreation attempt")
528
+ else:
529
+ print("❌ No valid command to start tunnel")
482
530
  return None
483
531
 
484
532
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unitlab
3
- Version: 2.3.17
3
+ Version: 2.3.20
4
4
  Home-page: https://github.com/teamunitlab/unitlab-sdk
5
5
  Author: Unitlab Inc.
6
6
  Author-email: team@unitlab.ai
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes