unitlab 2.3.7__tar.gz → 2.3.9__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.7
3
+ Version: 2.3.9
4
4
  Home-page: https://github.com/teamunitlab/unitlab-sdk
5
5
  Author: Unitlab Inc.
6
6
  Author-email: team@unitlab.ai
@@ -24,3 +24,4 @@ Requires-Dist: validators
24
24
  Requires-Dist: psutil
25
25
  Requires-Dist: pyyaml
26
26
  Requires-Dist: jupyter
27
+ Requires-Dist: python-dotenv
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
2
2
 
3
3
  setup(
4
4
  name="unitlab",
5
- version="2.3.7",
5
+ version="2.3.9",
6
6
  license="MIT",
7
7
  author="Unitlab Inc.",
8
8
  author_email="team@unitlab.ai",
@@ -32,6 +32,7 @@ setup(
32
32
  'psutil',
33
33
  'pyyaml',
34
34
  'jupyter',
35
+ 'python-dotenv',
35
36
 
36
37
  ],
37
38
  entry_points={
@@ -283,25 +283,24 @@ class UnitlabClient:
283
283
  self.device_id = device_id
284
284
  self.base_domain = base_domain
285
285
 
286
- # Initialize tunnel manager if available
287
- # Use API-based tunnel if API token is available
288
- print(os.getenv('CLOUDFLARE_API_TOKEN'), 'api tokennn')
289
-
290
- if os.getenv("CLOUDFLARE_API_TOKEN"):
286
+ # Always use API-based tunnel now that we have hardcoded credentials
287
+ # This provides better SSH/Jupyter separation with j/s prefixes
288
+ try:
291
289
  logger.info("Using API-based Cloudflare tunnel management")
292
-
293
290
  self.tunnel_manager = CloudflareAPITunnel(base_domain, device_id)
294
291
  self.jupyter_url = self.tunnel_manager.jupyter_url
295
292
  self.ssh_url = self.tunnel_manager.ssh_url
296
- elif CloudflareTunnel:
297
- logger.info("Using service token Cloudflare tunnel")
298
- self.tunnel_manager = CloudflareTunnel(base_domain, device_id)
299
- self.jupyter_url = self.tunnel_manager.jupyter_url
300
- self.ssh_url = self.tunnel_manager.ssh_url
301
- else:
302
- self.tunnel_manager = None
303
- self.jupyter_url = f"https://jupyter-{device_id}.{base_domain}"
304
- self.ssh_url = f"https://ssh-{device_id}.{base_domain}"
293
+ except Exception as e:
294
+ logger.warning(f"Failed to initialize API tunnel, falling back: {e}")
295
+ if CloudflareTunnel:
296
+ logger.info("Using service token Cloudflare tunnel")
297
+ self.tunnel_manager = CloudflareTunnel(base_domain, device_id)
298
+ self.jupyter_url = self.tunnel_manager.jupyter_url
299
+ self.ssh_url = self.tunnel_manager.ssh_url
300
+ else:
301
+ self.tunnel_manager = None
302
+ self.jupyter_url = f"https://jupyter-{device_id}.{base_domain}"
303
+ self.ssh_url = f"https://ssh-{device_id}.{base_domain}"
305
304
 
306
305
  # Setup signal handlers
307
306
  signal.signal(signal.SIGINT, self._handle_shutdown)
@@ -34,15 +34,17 @@ class CloudflareAPITunnel:
34
34
  # Clean device ID for subdomain
35
35
  self.clean_device_id = device_id.replace('-', '').replace('_', '').lower()[:20]
36
36
 
37
- # Cloudflare IDs (hardcoded for now, can move to env vars)
37
+ # Cloudflare IDs - hardcoded for zero-config experience
38
38
  self.zone_id = "78182c3883adad79d8f1026851a68176"
39
39
  self.account_id = "c91192ae20a5d43f65e087550d8dc89b"
40
40
  self.tunnel_id = "0777fc10-49c4-472d-8661-f60d80d6184d" # unitlab-agent tunnel
41
41
 
42
- # API token from environment
43
- self.api_token = os.getenv("CLOUDFLARE_API_TOKEN")
42
+ # API token - hardcoded for zero-config experience
43
+ # This token only has DNS edit permissions for 1scan.uz - limited scope for safety
44
+ self.api_token = "LJLe6QMOtpN0MeuLQ05_zUKKxVm4vEibkC8lxSJd"
45
+
44
46
  if not self.api_token:
45
- logger.warning("CLOUDFLARE_API_TOKEN not set. API features will be disabled.")
47
+ logger.warning("Using fallback tunnel configuration without API management.")
46
48
 
47
49
  # API setup
48
50
  self.api_base = "https://api.cloudflare.com/client/v4"
@@ -60,7 +62,13 @@ class CloudflareAPITunnel:
60
62
 
61
63
  self.tunnel_process = None
62
64
  self.created_dns_records = []
63
- self.binary_manager = CloudflaredBinaryManager()
65
+
66
+ # Try to initialize binary manager, but don't fail if it doesn't work
67
+ try:
68
+ self.binary_manager = CloudflaredBinaryManager()
69
+ except Exception as e:
70
+ logger.warning(f"Binary manager initialization failed: {e}")
71
+ self.binary_manager = None
64
72
 
65
73
  def create_dns_records(self):
66
74
  """
@@ -207,14 +215,14 @@ class CloudflareAPITunnel:
207
215
  self.create_dns_records()
208
216
  self.update_tunnel_config()
209
217
 
210
- # Get cloudflared binary
211
- cloudflared_path = self.binary_manager.get_binary_path()
218
+ # Ensure cloudflared is available
219
+ cloudflared_path = self._ensure_cloudflared()
220
+ if not cloudflared_path:
221
+ raise RuntimeError("Failed to obtain cloudflared binary")
212
222
 
213
- # Use the existing service token from environment
214
- service_token = os.getenv(
215
- "CLOUDFLARE_TUNNEL_TOKEN",
216
- "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakxUazBaalF0WXpjek1tSm1ZVGt4WlRRMCJ9"
217
- )
223
+ # Use the service token - hardcoded for zero-config experience
224
+ # This token can ONLY run the tunnel, cannot modify or delete it (safe to embed)
225
+ service_token = "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakxUazBaalF0WXpjek1tSm1ZVGt4WlRRMCJ9"
218
226
 
219
227
  # Start tunnel with service token
220
228
  cmd = [
@@ -269,6 +277,74 @@ class CloudflareAPITunnel:
269
277
  self.tunnel_process.wait(timeout=5)
270
278
  print("Tunnel stopped")
271
279
 
280
+ def _ensure_cloudflared(self):
281
+ """
282
+ Ensure cloudflared binary is available
283
+ Downloads it if necessary
284
+ """
285
+ print("🔍 Checking for cloudflared binary...")
286
+
287
+ # Try binary manager first
288
+ if self.binary_manager:
289
+ try:
290
+ path = self.binary_manager.get_binary_path()
291
+ print(f"✅ Using cloudflared from binary manager: {path}")
292
+ return path
293
+ except Exception as e:
294
+ logger.warning(f"Binary manager failed, will download directly: {e}")
295
+
296
+ # Direct download fallback - simplified version
297
+ import platform
298
+ import urllib.request
299
+
300
+ cache_dir = Path.home() / '.unitlab' / 'bin'
301
+ cache_dir.mkdir(parents=True, exist_ok=True)
302
+
303
+ cloudflared_path = cache_dir / 'cloudflared'
304
+ if platform.system() == 'Windows':
305
+ cloudflared_path = cache_dir / 'cloudflared.exe'
306
+
307
+ # If already exists, use it
308
+ if cloudflared_path.exists():
309
+ print(f"✅ Using cached cloudflared: {cloudflared_path}")
310
+ return str(cloudflared_path)
311
+
312
+ # Download based on platform
313
+ system = platform.system().lower()
314
+ machine = platform.machine().lower()
315
+
316
+ print(f"📥 Downloading cloudflared for {system}/{machine}...")
317
+
318
+ if system == 'linux':
319
+ if machine in ['x86_64', 'amd64']:
320
+ url = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64'
321
+ elif machine in ['aarch64', 'arm64']:
322
+ url = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64'
323
+ else:
324
+ url = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-386'
325
+ elif system == 'darwin':
326
+ url = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz'
327
+ elif system == 'windows':
328
+ url = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe'
329
+ else:
330
+ raise RuntimeError(f"Unsupported platform: {system}")
331
+
332
+ try:
333
+ # Download the file
334
+ urllib.request.urlretrieve(url, cloudflared_path)
335
+
336
+ # Make executable on Unix
337
+ if system != 'windows':
338
+ import stat
339
+ cloudflared_path.chmod(cloudflared_path.stat().st_mode | stat.S_IEXEC)
340
+
341
+ print(f"✅ Downloaded cloudflared to: {cloudflared_path}")
342
+ return str(cloudflared_path)
343
+
344
+ except Exception as e:
345
+ print(f"❌ Failed to download cloudflared: {e}")
346
+ raise RuntimeError(f"Could not download cloudflared: {e}")
347
+
272
348
  def cleanup_dns(self):
273
349
  """
274
350
  Remove created DNS records (optional cleanup)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unitlab
3
- Version: 2.3.7
3
+ Version: 2.3.9
4
4
  Home-page: https://github.com/teamunitlab/unitlab-sdk
5
5
  Author: Unitlab Inc.
6
6
  Author-email: team@unitlab.ai
@@ -24,3 +24,4 @@ Requires-Dist: validators
24
24
  Requires-Dist: psutil
25
25
  Requires-Dist: pyyaml
26
26
  Requires-Dist: jupyter
27
+ Requires-Dist: python-dotenv
@@ -7,3 +7,4 @@ validators
7
7
  psutil
8
8
  pyyaml
9
9
  jupyter
10
+ python-dotenv
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes