unitlab 2.3.40__py3-none-any.whl → 2.3.42__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.
- unitlab/main.py +1 -1
- unitlab/persistent_tunnel.py +50 -38
- {unitlab-2.3.40.dist-info → unitlab-2.3.42.dist-info}/METADATA +1 -1
- unitlab-2.3.42.dist-info/RECORD +13 -0
- unitlab-2.3.40.dist-info/RECORD +0 -13
- {unitlab-2.3.40.dist-info → unitlab-2.3.42.dist-info}/WHEEL +0 -0
- {unitlab-2.3.40.dist-info → unitlab-2.3.42.dist-info}/entry_points.txt +0 -0
- {unitlab-2.3.40.dist-info → unitlab-2.3.42.dist-info}/licenses/LICENSE.md +0 -0
- {unitlab-2.3.40.dist-info → unitlab-2.3.42.dist-info}/top_level.txt +0 -0
unitlab/main.py
CHANGED
@@ -122,7 +122,7 @@ def dataset_download(
|
|
122
122
|
def run_agent(
|
123
123
|
api_key: API_KEY,
|
124
124
|
device_id: Annotated[str, typer.Option(help="Device ID")] = None,
|
125
|
-
base_domain: Annotated[str, typer.Option(help="Base domain for tunnels")] = "
|
125
|
+
base_domain: Annotated[str, typer.Option(help="Base domain for tunnels")] = "api-dev.unitlab.ai",
|
126
126
|
|
127
127
|
):
|
128
128
|
|
unitlab/persistent_tunnel.py
CHANGED
@@ -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
|
|
@@ -38,7 +38,7 @@ class PersistentTunnel:
|
|
38
38
|
|
39
39
|
# Clean device ID for subdomain
|
40
40
|
if device_id:
|
41
|
-
self.device_id = device_id.replace('
|
41
|
+
self.device_id = device_id.replace('_', '').replace('.', '').lower()[:30]
|
42
42
|
else:
|
43
43
|
import uuid
|
44
44
|
self.device_id = str(uuid.uuid4())[:8]
|
@@ -67,23 +67,23 @@ class PersistentTunnel:
|
|
67
67
|
"Content-Type": "application/json"
|
68
68
|
}
|
69
69
|
|
70
|
-
def get_or_create_tunnel(self):
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
+
|
81
82
|
def create_new_tunnel(self):
|
82
|
-
"""Create a
|
83
|
+
"""Create a new tunnel via Cloudflare API"""
|
83
84
|
print("🔧 Creating new tunnel: {}...".format(self.tunnel_name))
|
84
85
|
|
85
86
|
# Generate random tunnel secret (32 bytes)
|
86
|
-
import secrets
|
87
87
|
tunnel_secret = base64.b64encode(secrets.token_bytes(32)).decode()
|
88
88
|
|
89
89
|
url = "https://api.cloudflare.com/client/v4/accounts/{}/cfd_tunnel".format(self.cf_account_id)
|
@@ -107,12 +107,13 @@ class PersistentTunnel:
|
|
107
107
|
"TunnelID": self.tunnel_id
|
108
108
|
}
|
109
109
|
|
110
|
-
# Save credentials to file
|
110
|
+
# Save credentials to file
|
111
111
|
cred_file = "/tmp/tunnel-{}.json".format(self.tunnel_name)
|
112
112
|
with open(cred_file, 'w') as f:
|
113
113
|
json.dump(self.tunnel_credentials, f)
|
114
114
|
|
115
115
|
print("✅ Tunnel created: {}".format(self.tunnel_id))
|
116
|
+
print("✅ Credentials saved to: {}".format(cred_file))
|
116
117
|
return cred_file
|
117
118
|
else:
|
118
119
|
print("❌ Failed to create tunnel: {}".format(response.text))
|
@@ -150,26 +151,26 @@ class PersistentTunnel:
|
|
150
151
|
return False
|
151
152
|
|
152
153
|
# First, check if SSH DNS record exists and delete it
|
153
|
-
print("🔍 Checking for existing SSH DNS record: {}.{}".format(self.ssh_subdomain, self.domain))
|
154
|
-
list_url = "{}?name={}.{}".format(url, self.ssh_subdomain, self.domain)
|
155
|
-
list_response = requests.get(list_url, headers=headers)
|
156
|
-
|
157
|
-
if list_response.status_code == 200:
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
else:
|
172
|
-
|
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]))
|
173
174
|
|
174
175
|
# Wait a moment for DNS deletion to propagate
|
175
176
|
time.sleep(2)
|
@@ -262,15 +263,20 @@ class PersistentTunnel:
|
|
262
263
|
f.write("credentials-file: {}\n\n".format(cred_file))
|
263
264
|
f.write("ingress:\n")
|
264
265
|
|
265
|
-
|
266
|
+
# SSH service on dedicated subdomain (s{deviceid}.unitlab-ai.com)
|
266
267
|
f.write(" - hostname: {}.{}\n".format(self.ssh_subdomain, self.domain))
|
267
268
|
f.write(" service: ssh://localhost:22\n")
|
268
269
|
|
270
|
+
|
271
|
+
|
272
|
+
|
269
273
|
# API (more specific path goes first)
|
270
274
|
f.write(" - hostname: {}.{}\n".format(self.subdomain, self.domain))
|
271
275
|
f.write(" path: /api-agent/*\n")
|
272
276
|
f.write(" service: http://localhost:8001\n")
|
273
277
|
|
278
|
+
|
279
|
+
|
274
280
|
# Jupyter (general hostname for HTTP)
|
275
281
|
f.write(" - hostname: {}.{}\n".format(self.subdomain, self.domain))
|
276
282
|
f.write(" service: http://localhost:8888\n")
|
@@ -440,7 +446,7 @@ class PersistentTunnel:
|
|
440
446
|
# API credentials are hardcoded, so we're ready to go
|
441
447
|
|
442
448
|
# 1. Get existing or create new tunnel via API
|
443
|
-
cred_file = self.
|
449
|
+
cred_file = self.create_new_tunnel()
|
444
450
|
|
445
451
|
|
446
452
|
# 2. Create DNS record
|
@@ -490,6 +496,12 @@ class PersistentTunnel:
|
|
490
496
|
self.jupyter_process.terminate()
|
491
497
|
if self.tunnel_process:
|
492
498
|
self.tunnel_process.terminate()
|
499
|
+
try:
|
500
|
+
self.tunnel_process.wait(timeout=5)
|
501
|
+
except subprocess.TimeoutExpired:
|
502
|
+
self.tunnel_process.kill()
|
503
|
+
self.tunnel_process.wait()
|
504
|
+
print("✅ Tunnel stopped")
|
493
505
|
|
494
506
|
# # Optionally delete tunnel when stopping
|
495
507
|
# if self.tunnel_id:
|
@@ -0,0 +1,13 @@
|
|
1
|
+
unitlab/__init__.py,sha256=Wtk5kQ_MTlxtd3mxJIn2qHVK5URrVcasMMPjD3BtrVM,214
|
2
|
+
unitlab/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
3
|
+
unitlab/client.py,sha256=wqREtDuYc5ixeloPEGm0hp1sdUtB59sB1bIJjBcO1y0,25983
|
4
|
+
unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
|
5
|
+
unitlab/main.py,sha256=QA1xv1D-sA4Igga2knulJ99g66aaKFyjQCR2pyyo70w,4431
|
6
|
+
unitlab/persistent_tunnel.py,sha256=pBdmHrgXS-7CgQjq429W2DzflW8OfkeW9qQwu4r6z4g,22640
|
7
|
+
unitlab/utils.py,sha256=9gPRu-d6pbhSoVdll1GXe4eoz_uFYOSbYArFDQdlUZs,1922
|
8
|
+
unitlab-2.3.42.dist-info/licenses/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
|
9
|
+
unitlab-2.3.42.dist-info/METADATA,sha256=fl1snGGNMX5N7sKFRIx1KT0tGGniTy_dM1iZ7tVtXX0,1046
|
10
|
+
unitlab-2.3.42.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
+
unitlab-2.3.42.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
|
12
|
+
unitlab-2.3.42.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
|
13
|
+
unitlab-2.3.42.dist-info/RECORD,,
|
unitlab-2.3.40.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
unitlab/__init__.py,sha256=Wtk5kQ_MTlxtd3mxJIn2qHVK5URrVcasMMPjD3BtrVM,214
|
2
|
-
unitlab/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
3
|
-
unitlab/client.py,sha256=wqREtDuYc5ixeloPEGm0hp1sdUtB59sB1bIJjBcO1y0,25983
|
4
|
-
unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
|
5
|
-
unitlab/main.py,sha256=RbuCUaExvOEoA33T9O0hnr94R-L70RXyMjQshxRWR-o,4421
|
6
|
-
unitlab/persistent_tunnel.py,sha256=NkXoTK3yn2mVN3hHoUqSQI-8ph_yfBU_YgEFumynyvo,22313
|
7
|
-
unitlab/utils.py,sha256=9gPRu-d6pbhSoVdll1GXe4eoz_uFYOSbYArFDQdlUZs,1922
|
8
|
-
unitlab-2.3.40.dist-info/licenses/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
|
9
|
-
unitlab-2.3.40.dist-info/METADATA,sha256=Wy1EHazXknuZ_jJYBjcKB_iXOvP3X4Rtt0MEUeeSKwc,1046
|
10
|
-
unitlab-2.3.40.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
11
|
-
unitlab-2.3.40.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
|
12
|
-
unitlab-2.3.40.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
|
13
|
-
unitlab-2.3.40.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|