unitlab 2.3.34__tar.gz → 2.3.36__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.
- {unitlab-2.3.34/src/unitlab.egg-info → unitlab-2.3.36}/PKG-INFO +1 -1
- {unitlab-2.3.34 → unitlab-2.3.36}/setup.py +1 -1
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/client.py +1 -2
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/main.py +1 -1
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/persistent_tunnel.py +67 -40
- {unitlab-2.3.34 → unitlab-2.3.36/src/unitlab.egg-info}/PKG-INFO +1 -1
- {unitlab-2.3.34 → unitlab-2.3.36}/LICENSE.md +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/README.md +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/setup.cfg +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/__init__.py +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/__main__.py +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/exceptions.py +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab/utils.py +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab.egg-info/SOURCES.txt +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab.egg-info/dependency_links.txt +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab.egg-info/entry_points.txt +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab.egg-info/requires.txt +0 -0
- {unitlab-2.3.34 → unitlab-2.3.36}/src/unitlab.egg-info/top_level.txt +0 -0
@@ -406,8 +406,7 @@ class UnitlabClient:
|
|
406
406
|
else:
|
407
407
|
|
408
408
|
self.ssh_url = self.jupyter_url
|
409
|
-
|
410
|
-
|
409
|
+
|
411
410
|
if hasattr(self.tunnel_manager, 'tunnel_url') and self.tunnel_manager.tunnel_url:
|
412
411
|
self.jupyter_url = self.tunnel_manager.tunnel_url
|
413
412
|
if not hasattr(self.tunnel_manager, 'ssh_url'):
|
@@ -208,6 +208,57 @@ class PersistentTunnel:
|
|
208
208
|
|
209
209
|
return True
|
210
210
|
|
211
|
+
def create_access_application(self):
|
212
|
+
"""Create Cloudflare Access application for SSH with bypass policy"""
|
213
|
+
print("🔧 Creating Access application for SSH...")
|
214
|
+
|
215
|
+
# Create Access application
|
216
|
+
app_url = "https://api.cloudflare.com/client/v4/zones/{}/access/apps".format(self.cf_zone_id)
|
217
|
+
headers = self._get_headers()
|
218
|
+
|
219
|
+
app_data = {
|
220
|
+
"name": "SSH-{}".format(self.device_id),
|
221
|
+
"domain": "{}.{}".format(self.ssh_subdomain, self.domain),
|
222
|
+
"type": "ssh",
|
223
|
+
"session_duration": "24h",
|
224
|
+
"auto_redirect_to_identity": False
|
225
|
+
}
|
226
|
+
|
227
|
+
app_response = requests.post(app_url, headers=headers, json=app_data)
|
228
|
+
|
229
|
+
if app_response.status_code in [200, 201]:
|
230
|
+
app_id = app_response.json()["result"]["id"]
|
231
|
+
print("✅ Access application created: {}".format(app_id))
|
232
|
+
|
233
|
+
# Create bypass policy (no authentication required)
|
234
|
+
policy_url = "https://api.cloudflare.com/client/v4/zones/{}/access/apps/{}/policies".format(
|
235
|
+
self.cf_zone_id, app_id
|
236
|
+
)
|
237
|
+
|
238
|
+
policy_data = {
|
239
|
+
"name": "Public Access",
|
240
|
+
"decision": "bypass",
|
241
|
+
"include": [
|
242
|
+
{"everyone": {}}
|
243
|
+
],
|
244
|
+
"precedence": 1
|
245
|
+
}
|
246
|
+
|
247
|
+
policy_response = requests.post(policy_url, headers=headers, json=policy_data)
|
248
|
+
|
249
|
+
if policy_response.status_code in [200, 201]:
|
250
|
+
print("✅ Bypass policy created - SSH is publicly accessible")
|
251
|
+
return True
|
252
|
+
else:
|
253
|
+
print("⚠️ Could not create bypass policy: {}".format(policy_response.text[:200]))
|
254
|
+
return False
|
255
|
+
elif "already exists" in app_response.text:
|
256
|
+
print("⚠️ Access application already exists")
|
257
|
+
return True
|
258
|
+
else:
|
259
|
+
print("⚠️ Could not create Access application: {}".format(app_response.text[:200]))
|
260
|
+
return False
|
261
|
+
|
211
262
|
def create_tunnel_config(self, cred_file):
|
212
263
|
"""Create tunnel config file"""
|
213
264
|
config_file = "/tmp/tunnel-config-{}.yml".format(self.tunnel_name)
|
@@ -400,10 +451,13 @@ class PersistentTunnel:
|
|
400
451
|
# 2. Create DNS record
|
401
452
|
self.create_dns_record()
|
402
453
|
|
403
|
-
# 3. Create
|
454
|
+
# 3. Create Access application for SSH
|
455
|
+
self.create_access_application()
|
456
|
+
|
457
|
+
# 4. Create config
|
404
458
|
config_file = self.create_tunnel_config(cred_file)
|
405
459
|
|
406
|
-
#
|
460
|
+
# 5. Start services
|
407
461
|
self.start_jupyter()
|
408
462
|
self.start_api()
|
409
463
|
self.start_tunnel(config_file)
|
@@ -433,34 +487,7 @@ class PersistentTunnel:
|
|
433
487
|
self.stop()
|
434
488
|
return False
|
435
489
|
|
436
|
-
|
437
|
-
"""Fallback to quick tunnel"""
|
438
|
-
print("🔧 Using quick tunnel (temporary URL)...")
|
439
|
-
|
440
|
-
# Start Jupyter first
|
441
|
-
self.start_jupyter()
|
442
|
-
|
443
|
-
# Start quick tunnel
|
444
|
-
cloudflared = self.get_cloudflared_path()
|
445
|
-
cmd = [cloudflared, "tunnel", "--url", "http://localhost:8888"]
|
446
|
-
|
447
|
-
self.tunnel_process = subprocess.Popen(
|
448
|
-
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
449
|
-
)
|
450
|
-
|
451
|
-
# Get URL from output
|
452
|
-
for _ in range(30):
|
453
|
-
line = self.tunnel_process.stdout.readline()
|
454
|
-
if "trycloudflare.com" in line:
|
455
|
-
import re
|
456
|
-
match = re.search(r'https://[a-zA-Z0-9-]+\.trycloudflare\.com', line)
|
457
|
-
if match:
|
458
|
-
self.jupyter_url = match.group(0)
|
459
|
-
print("✅ Quick tunnel: {}".format(self.jupyter_url))
|
460
|
-
return True
|
461
|
-
time.sleep(0.5)
|
462
|
-
|
463
|
-
return False
|
490
|
+
|
464
491
|
|
465
492
|
def stop(self):
|
466
493
|
"""Stop everything"""
|
@@ -469,16 +496,16 @@ class PersistentTunnel:
|
|
469
496
|
if self.tunnel_process:
|
470
497
|
self.tunnel_process.terminate()
|
471
498
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
499
|
+
# # Optionally delete tunnel when stopping
|
500
|
+
# if self.tunnel_id:
|
501
|
+
# try:
|
502
|
+
# url = "https://api.cloudflare.com/client/v4/accounts/{}/cfd_tunnel/{}".format(
|
503
|
+
# self.cf_account_id, self.tunnel_id
|
504
|
+
# )
|
505
|
+
# requests.delete(url, headers=self._get_headers())
|
506
|
+
# print("🗑️ Tunnel deleted")
|
507
|
+
# except Exception:
|
508
|
+
# pass # Ignore cleanup errors
|
482
509
|
|
483
510
|
def run(self):
|
484
511
|
"""Run and keep alive"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|