unitlab 2.3.15__tar.gz → 2.3.17__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.15
3
+ Version: 2.3.17
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.15",
5
+ version="2.3.17",
6
6
  license="MIT",
7
7
  author="Unitlab Inc.",
8
8
  author_email="team@unitlab.ai",
@@ -142,12 +142,12 @@ class CloudflareAPITunnel:
142
142
  print(f"🔧 Configuring tunnel routes...")
143
143
 
144
144
  # Get current tunnel config first
145
- get_url = f"{self.api_base}/accounts/{self.account_id}/cfd_tunnel/{self.tunnel_id}/configurations"
145
+ get_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{self.tunnel_id}/configurations"
146
146
 
147
147
  try:
148
148
  # Get existing config
149
149
  response = requests.get(get_url, headers=self.headers)
150
- current_config = response.json()
150
+ current_config = response.json() if response.status_code == 200 else {}
151
151
 
152
152
  # Build new ingress rules
153
153
  new_ingress = [
@@ -190,7 +190,7 @@ class CloudflareAPITunnel:
190
190
  }
191
191
  }
192
192
 
193
- put_url = f"{self.api_base}/accounts/{self.account_id}/cfd_tunnel/{self.tunnel_id}/configurations"
193
+ put_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{self.tunnel_id}/configurations"
194
194
  response = requests.put(put_url, headers=self.headers, json=config_data)
195
195
 
196
196
  if response.status_code == 200:
@@ -231,9 +231,12 @@ class CloudflareAPITunnel:
231
231
  # Create new tunnel
232
232
  print(f"📦 Creating new tunnel: {tunnel_name}")
233
233
  create_url = f"{self.api_base}/accounts/{self.account_id}/tunnels"
234
+ # Generate secret first
235
+ tunnel_secret = os.urandom(32).hex()
236
+
234
237
  create_data = {
235
238
  "name": tunnel_name,
236
- "tunnel_secret": os.urandom(32).hex() # Generate random secret
239
+ "tunnel_secret": tunnel_secret
237
240
  }
238
241
 
239
242
  create_response = requests.post(create_url, headers=self.headers, json=create_data)
@@ -242,6 +245,9 @@ class CloudflareAPITunnel:
242
245
  existing_tunnel = create_response.json()['result']
243
246
  print(f"✅ Created tunnel: {tunnel_name}")
244
247
 
248
+ # Add the secret to the tunnel info (API doesn't return it)
249
+ existing_tunnel['tunnel_secret'] = tunnel_secret
250
+
245
251
  # Save credentials for this tunnel
246
252
  self._save_tunnel_credentials(existing_tunnel)
247
253
 
@@ -254,8 +260,13 @@ class CloudflareAPITunnel:
254
260
  print(f"❌ Failed to create tunnel: {create_response.text}")
255
261
  return None
256
262
  else:
257
- # Tunnel exists - update config file in case settings changed
258
- print(f"♻️ Updating configuration for existing tunnel")
263
+ # Tunnel exists - but we need to ensure it can be used
264
+ print(f"♻️ Found existing tunnel, setting up for use")
265
+
266
+ # Store tunnel info for later use
267
+ existing_tunnel['needs_token'] = True # Mark that we'll need to use token
268
+
269
+ # Configure tunnel routes (creates config file)
259
270
  self._configure_tunnel_routes(existing_tunnel['id'])
260
271
 
261
272
  # Ensure DNS records exist
@@ -308,19 +319,32 @@ class CloudflareAPITunnel:
308
319
  def _save_tunnel_credentials(self, tunnel_info):
309
320
  """
310
321
  Save tunnel credentials locally for this device
322
+ Credentials must be base64 encoded for cloudflared
311
323
  """
324
+ import base64
325
+ import json
326
+
312
327
  creds_dir = Path.home() / '.cloudflared'
313
328
  creds_dir.mkdir(exist_ok=True)
314
329
 
315
330
  creds_file = creds_dir / f"{tunnel_info['id']}.json"
316
331
 
332
+ # Get the secret - it should be hex string
333
+ secret_hex = tunnel_info.get('tunnel_secret') or tunnel_info.get('secret')
334
+ if secret_hex:
335
+ # Convert hex to bytes then to base64
336
+ secret_bytes = bytes.fromhex(secret_hex)
337
+ secret_b64 = base64.b64encode(secret_bytes).decode('ascii')
338
+ else:
339
+ print("⚠️ No tunnel secret found")
340
+ return None
341
+
317
342
  credentials = {
318
343
  "AccountTag": self.account_id,
319
- "TunnelSecret": tunnel_info.get('tunnel_secret') or tunnel_info.get('secret'),
344
+ "TunnelSecret": secret_b64, # Must be base64!
320
345
  "TunnelID": tunnel_info['id']
321
346
  }
322
347
 
323
- import json
324
348
  with open(creds_file, 'w') as f:
325
349
  json.dump(credentials, f)
326
350
 
@@ -334,11 +358,6 @@ class CloudflareAPITunnel:
334
358
  try:
335
359
  print("🚀 Starting Cloudflare tunnel...")
336
360
 
337
- # First, try to set up DNS and routes via API
338
- if self.api_token:
339
- self.create_dns_records()
340
- self.update_tunnel_config()
341
-
342
361
  # Ensure cloudflared is available
343
362
  cloudflared_path = self._ensure_cloudflared()
344
363
  if not cloudflared_path:
@@ -347,6 +366,12 @@ class CloudflareAPITunnel:
347
366
  # Create or get existing tunnel for this device
348
367
  device_tunnel = self.create_device_tunnel()
349
368
 
369
+ # Now set up DNS and routes via API after tunnel is created/found
370
+ if self.api_token and device_tunnel:
371
+ self.tunnel_id = device_tunnel['id'] # Ensure tunnel_id is set
372
+ self.create_dns_records()
373
+ self.update_tunnel_config()
374
+
350
375
  if not device_tunnel:
351
376
  print("❌ Could not create/find device tunnel")
352
377
  # Fallback to shared tunnel if API fails
@@ -368,34 +393,51 @@ class CloudflareAPITunnel:
368
393
 
369
394
  # Check if credentials file exists
370
395
  creds_file = Path.home() / '.cloudflared' / f"{tunnel_id}.json"
396
+ config_file = Path.home() / '.cloudflared' / f'config-{tunnel_id}.yml'
397
+ cmd = None # Initialize cmd
371
398
 
372
- if not creds_file.exists():
399
+ if not creds_file.exists() or device_tunnel.get('needs_token'):
373
400
  # Try to recreate credentials from stored secret
374
401
  if device_tunnel.get('tunnel_secret'):
375
402
  self._save_tunnel_credentials(device_tunnel)
403
+ # Continue to use credentials below
376
404
  else:
377
- print("⚠️ No credentials found, requesting from API...")
405
+ print("⚠️ No stored credentials, using tunnel token...")
378
406
  # Get token for this tunnel
379
407
  token_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{tunnel_id}/token"
380
408
  token_response = requests.get(token_url, headers=self.headers)
381
409
  if token_response.status_code == 200:
382
410
  token = token_response.json()['result']
383
- # Use token directly
384
- cmd = [
385
- cloudflared_path,
386
- "tunnel",
387
- "--no-autoupdate",
388
- "run",
389
- "--token",
390
- token
391
- ]
411
+
412
+ # Check if config file exists with ingress rules
413
+ if config_file.exists():
414
+ # Use token with config file for ingress rules
415
+ print(f" Using token with config file: {config_file}")
416
+ cmd = [
417
+ cloudflared_path,
418
+ "tunnel",
419
+ "--no-autoupdate",
420
+ "--config", str(config_file),
421
+ "run",
422
+ "--token", token
423
+ ]
424
+ else:
425
+ # Use token directly
426
+ print(f" Using token directly (no config file)")
427
+ cmd = [
428
+ cloudflared_path,
429
+ "tunnel",
430
+ "--no-autoupdate",
431
+ "run",
432
+ "--token", token
433
+ ]
392
434
  else:
393
435
  print("❌ Could not get tunnel token")
394
436
  return None
395
437
 
396
- if creds_file.exists():
438
+ # Only use credentials if we haven't already set cmd with token
439
+ if cmd is None and creds_file.exists():
397
440
  # Check if config file exists
398
- config_file = Path.home() / '.cloudflared' / f'config-{tunnel_id}.yml'
399
441
  if config_file.exists():
400
442
  # Run tunnel with config file (includes routes)
401
443
  cmd = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unitlab
3
- Version: 2.3.15
3
+ Version: 2.3.17
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