unitlab 2.3.13__py3-none-any.whl → 2.3.15__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 +67 -28
- unitlab/main.py +18 -4
- {unitlab-2.3.13.dist-info → unitlab-2.3.15.dist-info}/METADATA +1 -1
- {unitlab-2.3.13.dist-info → unitlab-2.3.15.dist-info}/RECORD +8 -8
- {unitlab-2.3.13.dist-info → unitlab-2.3.15.dist-info}/LICENSE.md +0 -0
- {unitlab-2.3.13.dist-info → unitlab-2.3.15.dist-info}/WHEEL +0 -0
- {unitlab-2.3.13.dist-info → unitlab-2.3.15.dist-info}/entry_points.txt +0 -0
- {unitlab-2.3.13.dist-info → unitlab-2.3.15.dist-info}/top_level.txt +0 -0
unitlab/cloudflare_api_tunnel.py
CHANGED
@@ -64,6 +64,7 @@ class CloudflareAPITunnel:
|
|
64
64
|
|
65
65
|
self.tunnel_process = None
|
66
66
|
self.created_dns_records = []
|
67
|
+
self.tunnel_config_file = None
|
67
68
|
|
68
69
|
# Try to initialize binary manager, but don't fail if it doesn't work
|
69
70
|
try:
|
@@ -252,6 +253,13 @@ class CloudflareAPITunnel:
|
|
252
253
|
else:
|
253
254
|
print(f"❌ Failed to create tunnel: {create_response.text}")
|
254
255
|
return None
|
256
|
+
else:
|
257
|
+
# Tunnel exists - update config file in case settings changed
|
258
|
+
print(f"♻️ Updating configuration for existing tunnel")
|
259
|
+
self._configure_tunnel_routes(existing_tunnel['id'])
|
260
|
+
|
261
|
+
# Ensure DNS records exist
|
262
|
+
self.create_dns_records()
|
255
263
|
|
256
264
|
return existing_tunnel
|
257
265
|
|
@@ -260,28 +268,42 @@ class CloudflareAPITunnel:
|
|
260
268
|
def _configure_tunnel_routes(self, tunnel_id):
|
261
269
|
"""
|
262
270
|
Configure ingress routes for the device tunnel
|
271
|
+
The tunnel needs to be configured with a config file, not via API
|
272
|
+
So we'll create a config file for it
|
263
273
|
"""
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
274
|
+
import yaml
|
275
|
+
|
276
|
+
# Create config file for this tunnel
|
277
|
+
config_dir = Path.home() / '.cloudflared'
|
278
|
+
config_dir.mkdir(exist_ok=True)
|
279
|
+
config_file = config_dir / f'config-{tunnel_id}.yml'
|
280
|
+
|
281
|
+
config = {
|
282
|
+
"tunnel": tunnel_id,
|
283
|
+
"credentials-file": str(config_dir / f"{tunnel_id}.json"),
|
284
|
+
"ingress": [
|
285
|
+
{
|
286
|
+
"hostname": f"{self.jupyter_subdomain}.{self.base_domain}",
|
287
|
+
"service": "http://localhost:8888",
|
288
|
+
"originRequest": {
|
289
|
+
"noTLSVerify": True
|
275
290
|
}
|
276
|
-
|
277
|
-
|
291
|
+
},
|
292
|
+
{
|
293
|
+
"hostname": f"{self.ssh_subdomain}.{self.base_domain}",
|
294
|
+
"service": "ssh://localhost:22"
|
295
|
+
},
|
296
|
+
{
|
297
|
+
"service": "http_status:404"
|
298
|
+
}
|
299
|
+
]
|
278
300
|
}
|
279
301
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
302
|
+
with open(config_file, 'w') as f:
|
303
|
+
yaml.dump(config, f)
|
304
|
+
|
305
|
+
print(f"✅ Created tunnel config: {config_file}")
|
306
|
+
self.tunnel_config_file = config_file
|
285
307
|
|
286
308
|
def _save_tunnel_credentials(self, tunnel_info):
|
287
309
|
"""
|
@@ -372,15 +394,27 @@ class CloudflareAPITunnel:
|
|
372
394
|
return None
|
373
395
|
|
374
396
|
if creds_file.exists():
|
375
|
-
#
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
397
|
+
# Check if config file exists
|
398
|
+
config_file = Path.home() / '.cloudflared' / f'config-{tunnel_id}.yml'
|
399
|
+
if config_file.exists():
|
400
|
+
# Run tunnel with config file (includes routes)
|
401
|
+
cmd = [
|
402
|
+
cloudflared_path,
|
403
|
+
"tunnel",
|
404
|
+
"--no-autoupdate",
|
405
|
+
"--config", str(config_file),
|
406
|
+
"run"
|
407
|
+
]
|
408
|
+
else:
|
409
|
+
# Fallback to credentials file only
|
410
|
+
cmd = [
|
411
|
+
cloudflared_path,
|
412
|
+
"tunnel",
|
413
|
+
"--no-autoupdate",
|
414
|
+
"--credentials-file", str(creds_file),
|
415
|
+
"run",
|
416
|
+
tunnel_id
|
417
|
+
]
|
384
418
|
|
385
419
|
self.tunnel_process = subprocess.Popen(
|
386
420
|
cmd,
|
@@ -418,12 +452,17 @@ class CloudflareAPITunnel:
|
|
418
452
|
def stop(self):
|
419
453
|
"""
|
420
454
|
Stop the tunnel if running
|
455
|
+
Note: We keep the tunnel configuration for next run
|
421
456
|
"""
|
422
457
|
if self.tunnel_process and self.tunnel_process.poll() is None:
|
423
458
|
print("Stopping tunnel...")
|
424
459
|
self.tunnel_process.terminate()
|
425
|
-
|
460
|
+
try:
|
461
|
+
self.tunnel_process.wait(timeout=5)
|
462
|
+
except subprocess.TimeoutExpired:
|
463
|
+
self.tunnel_process.kill()
|
426
464
|
print("Tunnel stopped")
|
465
|
+
print("ℹ️ Tunnel configuration preserved for next run")
|
427
466
|
|
428
467
|
def _ensure_cloudflared(self):
|
429
468
|
"""
|
unitlab/main.py
CHANGED
@@ -146,13 +146,27 @@ def run_agent(
|
|
146
146
|
if not device_id:
|
147
147
|
import uuid
|
148
148
|
import platform
|
149
|
+
from pathlib import Path
|
150
|
+
|
149
151
|
# Try environment variable first
|
150
152
|
device_id = os.getenv('DEVICE_ID')
|
151
153
|
if not device_id:
|
152
|
-
#
|
153
|
-
|
154
|
-
|
155
|
-
|
154
|
+
# Try to load saved device ID
|
155
|
+
device_id_file = Path.home() / '.unitlab' / 'device_id'
|
156
|
+
device_id_file.parent.mkdir(exist_ok=True, parents=True)
|
157
|
+
|
158
|
+
if device_id_file.exists():
|
159
|
+
device_id = device_id_file.read_text().strip()
|
160
|
+
print(f"📌 Using saved device ID: {device_id}")
|
161
|
+
else:
|
162
|
+
# Generate a unique ID based on hostname and random UUID
|
163
|
+
hostname = platform.node().replace('.', '-').replace(' ', '-')[:20]
|
164
|
+
random_suffix = str(uuid.uuid4())[:8]
|
165
|
+
device_id = f"{hostname}-{random_suffix}"
|
166
|
+
|
167
|
+
# Save for future runs
|
168
|
+
device_id_file.write_text(device_id)
|
169
|
+
print(f"📝 Generated and saved device ID: {device_id}")
|
156
170
|
|
157
171
|
|
158
172
|
# Create client and initialize device agent
|
@@ -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=b3qYgI5mWuU3yiFZLNRPBk1iu27P0xq36eEqGzaeays,23309
|
6
6
|
unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
|
7
|
-
unitlab/main.py,sha256=
|
7
|
+
unitlab/main.py,sha256=7gPZ_2n90sxDnq9oGZVKOkuifr-k7w2Tq3ZIldAUE8I,5877
|
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.15.dist-info/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
|
12
|
+
unitlab-2.3.15.dist-info/METADATA,sha256=yXLtmCrwoxTtnXz8Fdglewuokmnm9ylEPjcbbTCcrkw,844
|
13
|
+
unitlab-2.3.15.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
14
|
+
unitlab-2.3.15.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
|
15
|
+
unitlab-2.3.15.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
|
16
|
+
unitlab-2.3.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|