unitlab 2.3.39__py3-none-any.whl → 2.3.41__py3-none-any.whl

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.
@@ -14,7 +14,7 @@ from fastapi import FastAPI
14
14
  import uvicorn
15
15
  import threading
16
16
  import psutil
17
-
17
+ import secrets
18
18
 
19
19
  api = FastAPI()
20
20
 
@@ -56,24 +56,7 @@ class PersistentTunnel:
56
56
  self.jupyter_process = None
57
57
  self.tunnel_process = None
58
58
 
59
- def get_zone_id(self):
60
- """Get Zone ID for unitlab-ai.com"""
61
- print("🔍 Getting Zone ID for {}...".format(self.domain))
62
-
63
- url = "https://api.cloudflare.com/client/v4/zones"
64
- headers = self._get_headers()
65
- params = {"name": self.domain}
66
-
67
- response = requests.get(url, headers=headers, params=params)
68
- if response.status_code == 200:
69
- data = response.json()
70
- if data["result"]:
71
- self.cf_zone_id = data["result"][0]["id"]
72
- print("✅ Zone ID: {}".format(self.cf_zone_id))
73
- return self.cf_zone_id
74
-
75
- print("❌ Could not get Zone ID")
76
- return None
59
+
77
60
 
78
61
  def _get_headers(self):
79
62
  """Get API headers for Global API Key"""
@@ -84,23 +67,23 @@ class PersistentTunnel:
84
67
  "Content-Type": "application/json"
85
68
  }
86
69
 
87
- def get_or_create_tunnel(self):
88
- """Always create a new tunnel with unique name to avoid conflicts"""
89
- # Generate unique tunnel name to avoid conflicts
90
- import uuid
91
- unique_suffix = str(uuid.uuid4())[:8]
92
- self.tunnel_name = "agent-{}-{}".format(self.device_id, unique_suffix)
93
- print("🔧 Creating tunnel: {}...".format(self.tunnel_name))
94
-
95
- # Always create new tunnel
96
- return self.create_new_tunnel()
97
-
70
+ # def get_or_create_tunnel(self):
71
+ # """Always create a new tunnel with unique name to avoid conflicts"""
72
+ # # Generate unique tunnel name to avoid conflicts
73
+ # import uuid
74
+ # unique_suffix = str(uuid.uuid4())[:8]
75
+ # self.tunnel_name = "agent-{}-{}".format(self.device_id, unique_suffix)
76
+ # print("🔧 Creating tunnel: {}...".format(self.tunnel_name))
77
+
78
+ # # Always create new tunnel
79
+ # return self.create_new_tunnel()
80
+
81
+
98
82
  def create_new_tunnel(self):
99
- """Create a brand new tunnel"""
83
+ """Create a new tunnel via Cloudflare API"""
100
84
  print("🔧 Creating new tunnel: {}...".format(self.tunnel_name))
101
85
 
102
86
  # Generate random tunnel secret (32 bytes)
103
- import secrets
104
87
  tunnel_secret = base64.b64encode(secrets.token_bytes(32)).decode()
105
88
 
106
89
  url = "https://api.cloudflare.com/client/v4/accounts/{}/cfd_tunnel".format(self.cf_account_id)
@@ -124,12 +107,13 @@ class PersistentTunnel:
124
107
  "TunnelID": self.tunnel_id
125
108
  }
126
109
 
127
- # Save credentials to file with tunnel name (not ID) for consistency
110
+ # Save credentials to file
128
111
  cred_file = "/tmp/tunnel-{}.json".format(self.tunnel_name)
129
112
  with open(cred_file, 'w') as f:
130
113
  json.dump(self.tunnel_credentials, f)
131
114
 
132
115
  print("✅ Tunnel created: {}".format(self.tunnel_id))
116
+ print("✅ Credentials saved to: {}".format(cred_file))
133
117
  return cred_file
134
118
  else:
135
119
  print("❌ Failed to create tunnel: {}".format(response.text))
@@ -142,9 +126,7 @@ class PersistentTunnel:
142
126
 
143
127
  print("🔧 Creating DNS records...")
144
128
 
145
- # Get zone ID if we don't have it
146
- if self.cf_zone_id == "NEED_ZONE_ID_FOR_1SCAN_UZ":
147
- self.get_zone_id()
129
+ # self.get_zone_id()
148
130
 
149
131
  url = "https://api.cloudflare.com/client/v4/zones/{}/dns_records".format(self.cf_zone_id)
150
132
  headers = self._get_headers()
@@ -168,7 +150,32 @@ class PersistentTunnel:
168
150
  print("❌ Failed to create main DNS: {}".format(response.text[:200]))
169
151
  return False
170
152
 
171
- # Create SSH subdomain record (s{deviceid}.unitlab-ai.com)
153
+ # First, check if SSH DNS record exists and delete it
154
+ print("🔍 Checking for existing SSH DNS record: {}.{}".format(self.ssh_subdomain, self.domain))
155
+ list_url = "{}?name={}.{}".format(url, self.ssh_subdomain, self.domain)
156
+ list_response = requests.get(list_url, headers=headers)
157
+
158
+ if list_response.status_code == 200:
159
+ records = list_response.json().get("result", [])
160
+ print("Found {} existing DNS records".format(len(records)))
161
+ print('this is new version')
162
+ for record in records:
163
+ if record["name"] == "{}.{}".format(self.ssh_subdomain, self.domain):
164
+ record_id = record["id"]
165
+ print("🗑️ Deleting old SSH DNS record: {}".format(record_id))
166
+ delete_url = "{}/{}".format(url, record_id)
167
+ delete_response = requests.delete(delete_url, headers=headers)
168
+ if delete_response.status_code in [200, 204]:
169
+ print("✅ Deleted old SSH DNS record")
170
+ else:
171
+ print("⚠️ Could not delete old SSH DNS record: {}".format(delete_response.text[:200]))
172
+ else:
173
+ print("⚠️ Could not list DNS records: {}".format(list_response.text[:200]))
174
+
175
+ # Wait a moment for DNS deletion to propagate
176
+ time.sleep(2)
177
+
178
+ # Create new SSH subdomain record pointing to new tunnel
172
179
  ssh_data = {
173
180
  "type": "CNAME",
174
181
  "name": self.ssh_subdomain,
@@ -177,31 +184,22 @@ class PersistentTunnel:
177
184
  "ttl": 1
178
185
  }
179
186
 
187
+ print("📝 Creating SSH DNS record: {} -> {}".format(self.ssh_subdomain, self.tunnel_id))
180
188
  ssh_response = requests.post(url, headers=headers, json=ssh_data)
181
189
 
182
190
  if ssh_response.status_code in [200, 201]:
183
191
  print("✅ SSH DNS record created: {}.{}".format(self.ssh_subdomain, self.domain))
184
- elif "already exists" in ssh_response.text:
185
- print("⚠️ SSH DNS record already exists, deleting and recreating...")
186
- # Delete the old record and create new one
187
- list_url = "{}?name={}.{}".format(url, self.ssh_subdomain, self.domain)
188
- list_response = requests.get(list_url, headers=headers)
189
- if list_response.status_code == 200:
190
- records = list_response.json().get("result", [])
191
- if records:
192
- record_id = records[0]["id"]
193
- delete_url = "{}/{}".format(url, record_id)
194
- requests.delete(delete_url, headers=headers)
195
- print("Deleted old SSH DNS record")
196
- # Try again
197
- ssh_response = requests.post(url, headers=headers, json=ssh_data)
198
- if ssh_response.status_code in [200, 201]:
199
- print("✅ SSH DNS record recreated: {}.{}".format(self.ssh_subdomain, self.domain))
200
- else:
201
- print("❌ Failed to recreate SSH DNS: {}".format(ssh_response.text[:200]))
192
+ print(" Points to: {}.cfargotunnel.com".format(self.tunnel_id))
202
193
  else:
203
- print("❌ Could not create SSH DNS: {}".format(ssh_response.text[:200]))
204
- # SSH is optional, so we continue even if SSH DNS fails
194
+ print("❌ Failed to create SSH DNS: Status {} - {}".format(ssh_response.status_code, ssh_response.text))
195
+ # Try to parse error
196
+ try:
197
+ error_data = ssh_response.json()
198
+ if "errors" in error_data:
199
+ for error in error_data["errors"]:
200
+ print(" Error: {}".format(error.get("message", error)))
201
+ except:
202
+ pass
205
203
 
206
204
  return True
207
205
 
@@ -243,6 +241,7 @@ class PersistentTunnel:
243
241
 
244
242
  policy_response = requests.post(policy_url, headers=headers, json=policy_data)
245
243
 
244
+
246
245
  if policy_response.status_code in [200, 201]:
247
246
  print("✅ Bypass policy created - SSH is publicly accessible")
248
247
  return True
@@ -442,7 +441,7 @@ class PersistentTunnel:
442
441
  # API credentials are hardcoded, so we're ready to go
443
442
 
444
443
  # 1. Get existing or create new tunnel via API
445
- cred_file = self.get_or_create_tunnel()
444
+ cred_file = self.create_new_tunnel()
446
445
 
447
446
 
448
447
  # 2. Create DNS record
@@ -492,6 +491,12 @@ class PersistentTunnel:
492
491
  self.jupyter_process.terminate()
493
492
  if self.tunnel_process:
494
493
  self.tunnel_process.terminate()
494
+ try:
495
+ self.tunnel_process.wait(timeout=5)
496
+ except subprocess.TimeoutExpired:
497
+ self.tunnel_process.kill()
498
+ self.tunnel_process.wait()
499
+ print("✅ Tunnel stopped")
495
500
 
496
501
  # # Optionally delete tunnel when stopping
497
502
  # if self.tunnel_id:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitlab
3
- Version: 2.3.39
3
+ Version: 2.3.41
4
4
  Home-page: https://github.com/teamunitlab/unitlab-sdk
5
5
  Author: Unitlab Inc.
6
6
  Author-email: team@unitlab.ai
@@ -3,11 +3,11 @@ unitlab/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
3
  unitlab/client.py,sha256=wqREtDuYc5ixeloPEGm0hp1sdUtB59sB1bIJjBcO1y0,25983
4
4
  unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
5
5
  unitlab/main.py,sha256=RbuCUaExvOEoA33T9O0hnr94R-L70RXyMjQshxRWR-o,4421
6
- unitlab/persistent_tunnel.py,sha256=CcZYD3fBN6WP_PzvBIomTMakfsR4GtlsfBarUWflBi0,22366
6
+ unitlab/persistent_tunnel.py,sha256=Ed_hRCsRT1I7UfpcAKfyVInNLfupk_hpgzFAz1GDUe8,22596
7
7
  unitlab/utils.py,sha256=9gPRu-d6pbhSoVdll1GXe4eoz_uFYOSbYArFDQdlUZs,1922
8
- unitlab-2.3.39.dist-info/licenses/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
9
- unitlab-2.3.39.dist-info/METADATA,sha256=3Y0CIEPDzVkfL3dcfpbND4dy3QWzNZo00Ir45YBKDmk,1046
10
- unitlab-2.3.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- unitlab-2.3.39.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
12
- unitlab-2.3.39.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
13
- unitlab-2.3.39.dist-info/RECORD,,
8
+ unitlab-2.3.41.dist-info/licenses/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
9
+ unitlab-2.3.41.dist-info/METADATA,sha256=DIzDm3lxsOVqI1STlrGcDf7pZybOCrqnCCyJ1BNVMoU,1046
10
+ unitlab-2.3.41.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ unitlab-2.3.41.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
12
+ unitlab-2.3.41.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
13
+ unitlab-2.3.41.dist-info/RECORD,,