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.
- unitlab/persistent_tunnel.py +63 -58
- {unitlab-2.3.39.dist-info → unitlab-2.3.41.dist-info}/METADATA +1 -1
- {unitlab-2.3.39.dist-info → unitlab-2.3.41.dist-info}/RECORD +7 -7
- {unitlab-2.3.39.dist-info → unitlab-2.3.41.dist-info}/WHEEL +0 -0
- {unitlab-2.3.39.dist-info → unitlab-2.3.41.dist-info}/entry_points.txt +0 -0
- {unitlab-2.3.39.dist-info → unitlab-2.3.41.dist-info}/licenses/LICENSE.md +0 -0
- {unitlab-2.3.39.dist-info → unitlab-2.3.41.dist-info}/top_level.txt +0 -0
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
|
|
@@ -56,24 +56,7 @@ class PersistentTunnel:
|
|
56
56
|
self.jupyter_process = None
|
57
57
|
self.tunnel_process = None
|
58
58
|
|
59
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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("❌
|
204
|
-
#
|
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.
|
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:
|
@@ -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=
|
6
|
+
unitlab/persistent_tunnel.py,sha256=Ed_hRCsRT1I7UfpcAKfyVInNLfupk_hpgzFAz1GDUe8,22596
|
7
7
|
unitlab/utils.py,sha256=9gPRu-d6pbhSoVdll1GXe4eoz_uFYOSbYArFDQdlUZs,1922
|
8
|
-
unitlab-2.3.
|
9
|
-
unitlab-2.3.
|
10
|
-
unitlab-2.3.
|
11
|
-
unitlab-2.3.
|
12
|
-
unitlab-2.3.
|
13
|
-
unitlab-2.3.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|