unitlab 2.3.12__py3-none-any.whl → 2.3.13__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/cloudflare_api_tunnel.py +158 -11
- {unitlab-2.3.12.dist-info → unitlab-2.3.13.dist-info}/METADATA +1 -1
- {unitlab-2.3.12.dist-info → unitlab-2.3.13.dist-info}/RECORD +7 -7
- {unitlab-2.3.12.dist-info → unitlab-2.3.13.dist-info}/LICENSE.md +0 -0
- {unitlab-2.3.12.dist-info → unitlab-2.3.13.dist-info}/WHEEL +0 -0
- {unitlab-2.3.12.dist-info → unitlab-2.3.13.dist-info}/entry_points.txt +0 -0
- {unitlab-2.3.12.dist-info → unitlab-2.3.13.dist-info}/top_level.txt +0 -0
unitlab/cloudflare_api_tunnel.py
CHANGED
@@ -205,6 +205,106 @@ class CloudflareAPITunnel:
|
|
205
205
|
print(" Assuming routes are configured in dashboard.")
|
206
206
|
return True
|
207
207
|
|
208
|
+
def create_device_tunnel(self):
|
209
|
+
"""
|
210
|
+
Create a unique tunnel for this device if it doesn't exist
|
211
|
+
"""
|
212
|
+
tunnel_name = f"device-{self.clean_device_id}"
|
213
|
+
print(f"🔍 Checking for tunnel: {tunnel_name}")
|
214
|
+
|
215
|
+
# Check if tunnel already exists
|
216
|
+
list_url = f"{self.api_base}/accounts/{self.account_id}/tunnels"
|
217
|
+
response = requests.get(list_url, headers=self.headers)
|
218
|
+
|
219
|
+
if response.status_code == 200:
|
220
|
+
tunnels = response.json().get('result', [])
|
221
|
+
existing_tunnel = None
|
222
|
+
|
223
|
+
for tunnel in tunnels:
|
224
|
+
if tunnel['name'] == tunnel_name:
|
225
|
+
existing_tunnel = tunnel
|
226
|
+
print(f"✅ Found existing tunnel: {tunnel_name}")
|
227
|
+
break
|
228
|
+
|
229
|
+
if not existing_tunnel:
|
230
|
+
# Create new tunnel
|
231
|
+
print(f"📦 Creating new tunnel: {tunnel_name}")
|
232
|
+
create_url = f"{self.api_base}/accounts/{self.account_id}/tunnels"
|
233
|
+
create_data = {
|
234
|
+
"name": tunnel_name,
|
235
|
+
"tunnel_secret": os.urandom(32).hex() # Generate random secret
|
236
|
+
}
|
237
|
+
|
238
|
+
create_response = requests.post(create_url, headers=self.headers, json=create_data)
|
239
|
+
|
240
|
+
if create_response.status_code in [200, 201]:
|
241
|
+
existing_tunnel = create_response.json()['result']
|
242
|
+
print(f"✅ Created tunnel: {tunnel_name}")
|
243
|
+
|
244
|
+
# Save credentials for this tunnel
|
245
|
+
self._save_tunnel_credentials(existing_tunnel)
|
246
|
+
|
247
|
+
# Configure tunnel routes
|
248
|
+
self._configure_tunnel_routes(existing_tunnel['id'])
|
249
|
+
|
250
|
+
# Create DNS records for this device
|
251
|
+
self.create_dns_records()
|
252
|
+
else:
|
253
|
+
print(f"❌ Failed to create tunnel: {create_response.text}")
|
254
|
+
return None
|
255
|
+
|
256
|
+
return existing_tunnel
|
257
|
+
|
258
|
+
return None
|
259
|
+
|
260
|
+
def _configure_tunnel_routes(self, tunnel_id):
|
261
|
+
"""
|
262
|
+
Configure ingress routes for the device tunnel
|
263
|
+
"""
|
264
|
+
config_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{tunnel_id}/configurations"
|
265
|
+
|
266
|
+
config_data = {
|
267
|
+
"config": {
|
268
|
+
"ingress": [
|
269
|
+
{
|
270
|
+
"hostname": f"{self.jupyter_subdomain}.{self.base_domain}",
|
271
|
+
"service": "http://localhost:8888"
|
272
|
+
},
|
273
|
+
{
|
274
|
+
"service": "http_status:404"
|
275
|
+
}
|
276
|
+
]
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
response = requests.put(config_url, headers=self.headers, json=config_data)
|
281
|
+
if response.status_code == 200:
|
282
|
+
print(f"✅ Configured tunnel routes")
|
283
|
+
else:
|
284
|
+
print(f"⚠️ Could not configure routes: {response.status_code}")
|
285
|
+
|
286
|
+
def _save_tunnel_credentials(self, tunnel_info):
|
287
|
+
"""
|
288
|
+
Save tunnel credentials locally for this device
|
289
|
+
"""
|
290
|
+
creds_dir = Path.home() / '.cloudflared'
|
291
|
+
creds_dir.mkdir(exist_ok=True)
|
292
|
+
|
293
|
+
creds_file = creds_dir / f"{tunnel_info['id']}.json"
|
294
|
+
|
295
|
+
credentials = {
|
296
|
+
"AccountTag": self.account_id,
|
297
|
+
"TunnelSecret": tunnel_info.get('tunnel_secret') or tunnel_info.get('secret'),
|
298
|
+
"TunnelID": tunnel_info['id']
|
299
|
+
}
|
300
|
+
|
301
|
+
import json
|
302
|
+
with open(creds_file, 'w') as f:
|
303
|
+
json.dump(credentials, f)
|
304
|
+
|
305
|
+
print(f"💾 Saved credentials to: {creds_file}")
|
306
|
+
return creds_file
|
307
|
+
|
208
308
|
def start_tunnel_with_token(self):
|
209
309
|
"""
|
210
310
|
Start tunnel using the existing service token
|
@@ -222,18 +322,65 @@ class CloudflareAPITunnel:
|
|
222
322
|
if not cloudflared_path:
|
223
323
|
raise RuntimeError("Failed to obtain cloudflared binary")
|
224
324
|
|
225
|
-
#
|
226
|
-
|
227
|
-
service_token = "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakxTazBaalF0WXpjek1tSm1ZVGt4WlRRMCJ9"
|
325
|
+
# Create or get existing tunnel for this device
|
326
|
+
device_tunnel = self.create_device_tunnel()
|
228
327
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
"
|
233
|
-
"
|
234
|
-
|
235
|
-
|
236
|
-
|
328
|
+
if not device_tunnel:
|
329
|
+
print("❌ Could not create/find device tunnel")
|
330
|
+
# Fallback to shared tunnel if API fails
|
331
|
+
print("⚠️ Falling back to shared tunnel...")
|
332
|
+
service_token = "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakLTazBaalF0WXpjek1tSm1ZVGt4WlRRMCJ9"
|
333
|
+
cmd = [
|
334
|
+
cloudflared_path,
|
335
|
+
"tunnel",
|
336
|
+
"--no-autoupdate",
|
337
|
+
"run",
|
338
|
+
"--token",
|
339
|
+
service_token
|
340
|
+
]
|
341
|
+
else:
|
342
|
+
tunnel_id = device_tunnel['id']
|
343
|
+
tunnel_name = device_tunnel['name']
|
344
|
+
|
345
|
+
print(f"🚇 Starting tunnel: {tunnel_name} ({tunnel_id})")
|
346
|
+
|
347
|
+
# Check if credentials file exists
|
348
|
+
creds_file = Path.home() / '.cloudflared' / f"{tunnel_id}.json"
|
349
|
+
|
350
|
+
if not creds_file.exists():
|
351
|
+
# Try to recreate credentials from stored secret
|
352
|
+
if device_tunnel.get('tunnel_secret'):
|
353
|
+
self._save_tunnel_credentials(device_tunnel)
|
354
|
+
else:
|
355
|
+
print("⚠️ No credentials found, requesting from API...")
|
356
|
+
# Get token for this tunnel
|
357
|
+
token_url = f"{self.api_base}/accounts/{self.account_id}/tunnels/{tunnel_id}/token"
|
358
|
+
token_response = requests.get(token_url, headers=self.headers)
|
359
|
+
if token_response.status_code == 200:
|
360
|
+
token = token_response.json()['result']
|
361
|
+
# Use token directly
|
362
|
+
cmd = [
|
363
|
+
cloudflared_path,
|
364
|
+
"tunnel",
|
365
|
+
"--no-autoupdate",
|
366
|
+
"run",
|
367
|
+
"--token",
|
368
|
+
token
|
369
|
+
]
|
370
|
+
else:
|
371
|
+
print("❌ Could not get tunnel token")
|
372
|
+
return None
|
373
|
+
|
374
|
+
if creds_file.exists():
|
375
|
+
# Run tunnel with credentials file
|
376
|
+
cmd = [
|
377
|
+
cloudflared_path,
|
378
|
+
"tunnel",
|
379
|
+
"--no-autoupdate",
|
380
|
+
"--credentials-file", str(creds_file),
|
381
|
+
"run",
|
382
|
+
tunnel_id
|
383
|
+
]
|
237
384
|
|
238
385
|
self.tunnel_process = subprocess.Popen(
|
239
386
|
cmd,
|
@@ -2,15 +2,15 @@ unitlab/__init__.py,sha256=Wtk5kQ_MTlxtd3mxJIn2qHVK5URrVcasMMPjD3BtrVM,214
|
|
2
2
|
unitlab/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
|
3
3
|
unitlab/binary_manager.py,sha256=Q1v2Odm0hk_3g7jfDUJQfkjEbUbSjtuyo2JDUyWjDrk,5468
|
4
4
|
unitlab/client.py,sha256=V5fTgbprmMsnMwD_FPn7oZh0KK6hdnqB4BYuY4D-JRw,24558
|
5
|
-
unitlab/cloudflare_api_tunnel.py,sha256=
|
5
|
+
unitlab/cloudflare_api_tunnel.py,sha256=2rIGFAP9TMtOLGstqVUpVj5C0vCDdhkVRVHH41O5_MA,21624
|
6
6
|
unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
|
7
7
|
unitlab/main.py,sha256=h1WG6up6STt-2fJZAkqnxenwJ7kmkqnkfq5Zs4xLSeI,5257
|
8
8
|
unitlab/tunnel_config.py,sha256=7CiAqasfg26YQfJYXapCBQPSoqw4jIx6yR64saybLLo,8312
|
9
9
|
unitlab/tunnel_service_token.py,sha256=ji96a4s4W2cFJrHZle0zBD85Ac_T862-gCKzBUomrxM,3125
|
10
10
|
unitlab/utils.py,sha256=83ekAxxfXecFTg76Z62BGDybC_skKJHYoLyawCD9wGM,1920
|
11
|
-
unitlab-2.3.
|
12
|
-
unitlab-2.3.
|
13
|
-
unitlab-2.3.
|
14
|
-
unitlab-2.3.
|
15
|
-
unitlab-2.3.
|
16
|
-
unitlab-2.3.
|
11
|
+
unitlab-2.3.13.dist-info/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
|
12
|
+
unitlab-2.3.13.dist-info/METADATA,sha256=P46YSXOduMWVweKtJd5AfIJUq7sqNjHFgN_qd3o7Bh8,844
|
13
|
+
unitlab-2.3.13.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
14
|
+
unitlab-2.3.13.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
|
15
|
+
unitlab-2.3.13.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
|
16
|
+
unitlab-2.3.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|