unitlab 2.3.27__py3-none-any.whl → 2.3.29__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.
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Persistent Tunnel - Each device gets deviceid.1scan.uz
4
+ Uses Cloudflare API to create named tunnels
5
+ """
6
+
7
+ import subprocess
8
+ import requests
9
+ import json
10
+ import time
11
+ import os
12
+ import base64
13
+
14
+ class PersistentTunnel:
15
+ def __init__(self, device_id=None):
16
+ """Initialize with device ID"""
17
+
18
+ # Cloudflare credentials (hardcoded for simplicity)
19
+ self.cf_email = "uone2323@gmail.com"
20
+ self.cf_api_key = "1c634bd17ca6ade0eb91966323589fd98c72e" # Global API Key
21
+
22
+ # Account and Zone IDs
23
+ self.cf_account_id = "c91192ae20a5d43f65e087550d8dc89b" # Your account ID
24
+ self.cf_zone_id = "78182c3883adad79d8f1026851a68176" # Zone ID for 1scan.uz
25
+
26
+ # Clean device ID for subdomain
27
+ if device_id:
28
+ self.device_id = device_id.replace('-', '').replace('_', '').replace('.', '').lower()[:20]
29
+ else:
30
+ import uuid
31
+ self.device_id = str(uuid.uuid4())[:8]
32
+
33
+ self.tunnel_name = "agent-{}".format(self.device_id)
34
+ self.subdomain = self.device_id
35
+ self.domain = "1scan.uz"
36
+ self.jupyter_url = "https://{}.{}".format(self.subdomain, self.domain)
37
+
38
+ self.tunnel_id = None
39
+ self.tunnel_credentials = None
40
+ self.jupyter_process = None
41
+ self.tunnel_process = None
42
+
43
+ def get_zone_id(self):
44
+ """Get Zone ID for 1scan.uz"""
45
+ print("🔍 Getting Zone ID for {}...".format(self.domain))
46
+
47
+ url = "https://api.cloudflare.com/client/v4/zones"
48
+ headers = self._get_headers()
49
+ params = {"name": self.domain}
50
+
51
+ response = requests.get(url, headers=headers, params=params)
52
+ if response.status_code == 200:
53
+ data = response.json()
54
+ if data["result"]:
55
+ self.cf_zone_id = data["result"][0]["id"]
56
+ print("✅ Zone ID: {}".format(self.cf_zone_id))
57
+ return self.cf_zone_id
58
+
59
+ print("❌ Could not get Zone ID")
60
+ return None
61
+
62
+ def _get_headers(self):
63
+ """Get API headers for Global API Key"""
64
+ return {
65
+ "X-Auth-Email": self.cf_email,
66
+ "X-Auth-Key": self.cf_api_key,
67
+ "Content-Type": "application/json"
68
+ }
69
+
70
+ def create_tunnel(self):
71
+ """Create a new tunnel via API"""
72
+ print("🔧 Creating tunnel: {}...".format(self.tunnel_name))
73
+
74
+ # Generate random tunnel secret (32 bytes)
75
+ import secrets
76
+ tunnel_secret = base64.b64encode(secrets.token_bytes(32)).decode()
77
+
78
+ url = "https://api.cloudflare.com/client/v4/accounts/{}/cfd_tunnel".format(self.cf_account_id)
79
+ headers = self._get_headers()
80
+
81
+ data = {
82
+ "name": self.tunnel_name,
83
+ "tunnel_secret": tunnel_secret
84
+ }
85
+
86
+ response = requests.post(url, headers=headers, json=data)
87
+
88
+ if response.status_code in [200, 201]:
89
+ result = response.json()["result"]
90
+ self.tunnel_id = result["id"]
91
+
92
+ # Create credentials JSON
93
+ self.tunnel_credentials = {
94
+ "AccountTag": self.cf_account_id,
95
+ "TunnelSecret": tunnel_secret,
96
+ "TunnelID": self.tunnel_id
97
+ }
98
+
99
+ # Save credentials to file
100
+ cred_file = "/tmp/tunnel-{}.json".format(self.tunnel_id)
101
+ with open(cred_file, 'w') as f:
102
+ json.dump(self.tunnel_credentials, f)
103
+
104
+ print("✅ Tunnel created: {}".format(self.tunnel_id))
105
+ return cred_file
106
+ else:
107
+ print("❌ Failed to create tunnel: {}".format(response.text[:200]))
108
+ return None
109
+
110
+ def create_dns_record(self):
111
+ """Create DNS CNAME record"""
112
+ if not self.tunnel_id:
113
+ return False
114
+
115
+ print("🔧 Creating DNS record: {}.{}...".format(self.subdomain, self.domain))
116
+
117
+ # Get zone ID if we don't have it
118
+ if self.cf_zone_id == "NEED_ZONE_ID_FOR_1SCAN_UZ":
119
+ self.get_zone_id()
120
+
121
+ url = "https://api.cloudflare.com/client/v4/zones/{}/dns_records".format(self.cf_zone_id)
122
+ headers = self._get_headers()
123
+
124
+ data = {
125
+ "type": "CNAME",
126
+ "name": self.subdomain,
127
+ "content": "{}.cfargotunnel.com".format(self.tunnel_id),
128
+ "proxied": True,
129
+ "ttl": 1
130
+ }
131
+
132
+ response = requests.post(url, headers=headers, json=data)
133
+
134
+ if response.status_code in [200, 201]:
135
+ print("✅ DNS record created")
136
+ return True
137
+ elif "already exists" in response.text:
138
+ print("⚠️ DNS record already exists")
139
+ return True
140
+ else:
141
+ print("❌ Failed to create DNS: {}".format(response.text[:200]))
142
+ return False
143
+
144
+ def create_tunnel_config(self, cred_file):
145
+ """Create tunnel config file"""
146
+ config = {
147
+ "ingress": [
148
+ {
149
+ "hostname": "{}.{}".format(self.subdomain, self.domain),
150
+ "service": "http://localhost:8888"
151
+ },
152
+ {
153
+ "service": "http_status:404"
154
+ }
155
+ ]
156
+ }
157
+
158
+ config_file = "/tmp/tunnel-config-{}.yml".format(self.tunnel_id)
159
+ with open(config_file, 'w') as f:
160
+ f.write("tunnel: {}\n".format(self.tunnel_id))
161
+ f.write("credentials-file: {}\n\n".format(cred_file))
162
+ f.write("ingress:\n")
163
+ f.write(" - hostname: {}.{}\n".format(self.subdomain, self.domain))
164
+ f.write(" service: http://localhost:8888\n")
165
+ f.write(" - service: http_status:404\n")
166
+
167
+ return config_file
168
+
169
+ def get_cloudflared_path(self):
170
+ """Get or download cloudflared"""
171
+ import shutil
172
+ if shutil.which("cloudflared"):
173
+ return "cloudflared"
174
+
175
+ local_bin = os.path.expanduser("~/.local/bin/cloudflared")
176
+ if os.path.exists(local_bin):
177
+ return local_bin
178
+
179
+ # Download
180
+ print("📦 Downloading cloudflared...")
181
+ import platform
182
+ system = platform.system().lower()
183
+ arch = "amd64" if "x86" in platform.machine() else "arm64"
184
+ url = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-{}".format(arch)
185
+
186
+ os.makedirs(os.path.dirname(local_bin), exist_ok=True)
187
+ subprocess.run("curl -L {} -o {}".format(url, local_bin), shell=True, capture_output=True)
188
+ subprocess.run("chmod +x {}".format(local_bin), shell=True)
189
+ return local_bin
190
+
191
+ def start_jupyter(self):
192
+ """Start Jupyter"""
193
+ print("🚀 Starting Jupyter...")
194
+
195
+ cmd = [
196
+ "jupyter", "notebook",
197
+ "--port", "8888",
198
+ "--no-browser",
199
+ "--ip", "0.0.0.0",
200
+ "--NotebookApp.token=''",
201
+ "--NotebookApp.password=''"
202
+ ]
203
+
204
+ self.jupyter_process = subprocess.Popen(
205
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
206
+ )
207
+
208
+ time.sleep(3)
209
+ print("✅ Jupyter started")
210
+ return True
211
+
212
+ def start_tunnel(self, config_file):
213
+ """Start tunnel with config"""
214
+ print("🔧 Starting tunnel...")
215
+
216
+ cloudflared = self.get_cloudflared_path()
217
+
218
+ cmd = [
219
+ cloudflared,
220
+ "tunnel",
221
+ "--config", config_file,
222
+ "run"
223
+ ]
224
+
225
+ self.tunnel_process = subprocess.Popen(
226
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
227
+ )
228
+
229
+ time.sleep(5)
230
+ print("✅ Tunnel running at {}".format(self.jupyter_url))
231
+ return True
232
+
233
+ def start(self):
234
+ """Main entry point"""
235
+ try:
236
+ print("="*50)
237
+ print("🌐 Persistent Tunnel with API")
238
+ print("Device: {}".format(self.device_id))
239
+ print("Target: {}.{}".format(self.subdomain, self.domain))
240
+ print("="*50)
241
+
242
+ # API credentials are hardcoded, so we're ready to go
243
+
244
+ # 1. Create tunnel via API
245
+ cred_file = self.create_tunnel()
246
+ if not cred_file:
247
+ print("⚠️ Falling back to quick tunnel")
248
+ return self.start_quick_tunnel()
249
+
250
+ # 2. Create DNS record
251
+ self.create_dns_record()
252
+
253
+ # 3. Create config
254
+ config_file = self.create_tunnel_config(cred_file)
255
+
256
+ # 4. Start services
257
+ self.start_jupyter()
258
+ self.start_tunnel(config_file)
259
+
260
+ print("\n" + "="*50)
261
+ print("🎉 SUCCESS! Persistent URL created:")
262
+ print(" {}".format(self.jupyter_url))
263
+ print(" Tunnel ID: {}".format(self.tunnel_id))
264
+ print("="*50)
265
+
266
+ return True
267
+
268
+ except Exception as e:
269
+ print("❌ Error: {}".format(e))
270
+ import traceback
271
+ traceback.print_exc()
272
+ self.stop()
273
+ return False
274
+
275
+ def start_quick_tunnel(self):
276
+ """Fallback to quick tunnel"""
277
+ print("🔧 Using quick tunnel (temporary URL)...")
278
+
279
+ # Start Jupyter first
280
+ self.start_jupyter()
281
+
282
+ # Start quick tunnel
283
+ cloudflared = self.get_cloudflared_path()
284
+ cmd = [cloudflared, "tunnel", "--url", "http://localhost:8888"]
285
+
286
+ self.tunnel_process = subprocess.Popen(
287
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
288
+ )
289
+
290
+ # Get URL from output
291
+ for _ in range(30):
292
+ line = self.tunnel_process.stdout.readline()
293
+ if "trycloudflare.com" in line:
294
+ import re
295
+ match = re.search(r'https://[a-zA-Z0-9-]+\.trycloudflare\.com', line)
296
+ if match:
297
+ self.jupyter_url = match.group(0)
298
+ print("✅ Quick tunnel: {}".format(self.jupyter_url))
299
+ return True
300
+ time.sleep(0.5)
301
+
302
+ return False
303
+
304
+ def stop(self):
305
+ """Stop everything"""
306
+ if self.jupyter_process:
307
+ self.jupyter_process.terminate()
308
+ if self.tunnel_process:
309
+ self.tunnel_process.terminate()
310
+
311
+ # Optionally delete tunnel when stopping
312
+ if self.tunnel_id:
313
+ try:
314
+ url = "https://api.cloudflare.com/client/v4/accounts/{}/cfd_tunnel/{}".format(
315
+ self.cf_account_id, self.tunnel_id
316
+ )
317
+ requests.delete(url, headers=self._get_headers())
318
+ print("🗑️ Tunnel deleted")
319
+ except Exception as e:
320
+ pass # Ignore cleanup errors
321
+
322
+ def run(self):
323
+ """Run and keep alive"""
324
+ try:
325
+ if self.start():
326
+ print("\nPress Ctrl+C to stop...")
327
+ while True:
328
+ time.sleep(1)
329
+ except KeyboardInterrupt:
330
+ print("\n⏹️ Shutting down...")
331
+ self.stop()
332
+
333
+
334
+ def main():
335
+ import platform
336
+ import uuid
337
+
338
+ hostname = platform.node().replace('.', '-')[:20]
339
+ device_id = "{}-{}".format(hostname, str(uuid.uuid4())[:8])
340
+
341
+ print("Device ID: {}".format(device_id))
342
+
343
+ tunnel = PersistentTunnel(device_id=device_id)
344
+ tunnel.run()
345
+
346
+
347
+ if __name__ == "__main__":
348
+ main()
unitlab/simple_tunnel.py CHANGED
@@ -61,13 +61,60 @@ class SimpleTunnel:
61
61
  print("✅ Jupyter started on port {}".format(port))
62
62
  return True
63
63
 
64
+ def get_cloudflared_path(self):
65
+ """Get cloudflared binary, download if needed"""
66
+ import os
67
+ import platform
68
+
69
+ # Check if cloudflared exists in system
70
+ try:
71
+ import shutil
72
+ if shutil.which("cloudflared"):
73
+ print("✅ Using system cloudflared")
74
+ return "cloudflared"
75
+ except:
76
+ pass
77
+
78
+ # Check local binary
79
+ local_bin = os.path.expanduser("~/.local/bin/cloudflared")
80
+ if os.path.exists(local_bin):
81
+ print("✅ Using existing cloudflared from ~/.local/bin")
82
+ return local_bin
83
+
84
+ # Download it
85
+ print("📦 Downloading cloudflared (this may take a moment)...")
86
+ system = platform.system().lower()
87
+ if system == "linux":
88
+ arch = "amd64" if "x86" in platform.machine() else "arm64"
89
+ url = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-{}".format(arch)
90
+
91
+ os.makedirs(os.path.expanduser("~/.local/bin"), exist_ok=True)
92
+ result = subprocess.run("curl -L {} -o {}".format(url, local_bin), shell=True, capture_output=True)
93
+ if result.returncode == 0:
94
+ subprocess.run("chmod +x {}".format(local_bin), shell=True)
95
+ print("✅ cloudflared downloaded successfully")
96
+ return local_bin
97
+ else:
98
+ print("❌ Failed to download cloudflared")
99
+ raise Exception("Could not download cloudflared")
100
+
101
+ raise Exception("Could not find or download cloudflared")
102
+
64
103
  def start_tunnel(self, local_port=8888):
65
104
  """Start cloudflared tunnel - simple and direct"""
66
105
  print("🔧 Starting Cloudflare tunnel...")
67
106
 
107
+ # Get cloudflared (downloads if needed)
108
+ try:
109
+ cloudflared = self.get_cloudflared_path()
110
+ except Exception as e:
111
+ print("⚠️ Error getting cloudflared: {}".format(e))
112
+ # Fallback - try to use system cloudflared anyway
113
+ cloudflared = "cloudflared"
114
+
68
115
  # Simple command - just run the tunnel
69
116
  cmd = [
70
- "cloudflared", # Use system cloudflared
117
+ cloudflared,
71
118
  "tunnel",
72
119
  "run",
73
120
  "--url", "http://127.0.0.1:{}".format(local_port),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unitlab
3
- Version: 2.3.27
3
+ Version: 2.3.29
4
4
  Home-page: https://github.com/teamunitlab/unitlab-sdk
5
5
  Author: Unitlab Inc.
6
6
  Author-email: team@unitlab.ai
@@ -0,0 +1,23 @@
1
+ unitlab/__init__.py,sha256=Wtk5kQ_MTlxtd3mxJIn2qHVK5URrVcasMMPjD3BtrVM,214
2
+ unitlab/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
+ unitlab/api_tunnel.py,sha256=SzDKFmxUg713KTkysc8qUnSmkfRc_dS3Cqrw2ONjn8I,8259
4
+ unitlab/auto_tunnel.py,sha256=Q4YyxrKOvM6jB1lQZd-QcHwt5SuMa60MpKWKEWF4fhY,5495
5
+ unitlab/binary_manager.py,sha256=Q1v2Odm0hk_3g7jfDUJQfkjEbUbSjtuyo2JDUyWjDrk,5468
6
+ unitlab/client.py,sha256=roVX8yq1x8LW1XEFuic4G-Cq1QEjsk2CySGkiJbxA78,25759
7
+ unitlab/cloudflare_api_tunnel.py,sha256=XgDOQ-ISNDAJOlbKp96inGix3An_eBnAQ2pORcGBM40,14061
8
+ unitlab/cloudflare_api_tunnel_backup.py,sha256=dG5Vax0JqrF2i-zxAFB-kNGyVSFR01-ovalwuJELqpo,28489
9
+ unitlab/dynamic_tunnel.py,sha256=fHPMouaY2q1N7e4jyre34ZeWk2mx7MKanoPfRnLNmc8,8980
10
+ unitlab/easy_tunnel.py,sha256=yfTGv7i9wtqMpMagpIrIQTrd3jknYwQ6IUgFGbcitKM,6735
11
+ unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
12
+ unitlab/main.py,sha256=7gPZ_2n90sxDnq9oGZVKOkuifr-k7w2Tq3ZIldAUE8I,5877
13
+ unitlab/persistent_tunnel.py,sha256=0ubhsUOJUpDKG0xo18e6mN1V4pxNQvNFjylC1J1QglA,11712
14
+ unitlab/simple_tunnel.py,sha256=vWgVYFEbPoGCHmumujNrfBnDPuUCZgQJkVO3IvdygQA,6812
15
+ unitlab/tunnel_config.py,sha256=7CiAqasfg26YQfJYXapCBQPSoqw4jIx6yR64saybLLo,8312
16
+ unitlab/tunnel_service_token.py,sha256=ji96a4s4W2cFJrHZle0zBD85Ac_T862-gCKzBUomrxM,3125
17
+ unitlab/utils.py,sha256=83ekAxxfXecFTg76Z62BGDybC_skKJHYoLyawCD9wGM,1920
18
+ unitlab-2.3.29.dist-info/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
19
+ unitlab-2.3.29.dist-info/METADATA,sha256=OG1XzALj4PXPvmcrJ4l15-DXdelWtgxm5y0Wo9k9uZ8,844
20
+ unitlab-2.3.29.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
21
+ unitlab-2.3.29.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
22
+ unitlab-2.3.29.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
23
+ unitlab-2.3.29.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- unitlab/__init__.py,sha256=Wtk5kQ_MTlxtd3mxJIn2qHVK5URrVcasMMPjD3BtrVM,214
2
- unitlab/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
- unitlab/binary_manager.py,sha256=Q1v2Odm0hk_3g7jfDUJQfkjEbUbSjtuyo2JDUyWjDrk,5468
4
- unitlab/client.py,sha256=TAv9ePzs8gGAgqGXkiGCxD-cI5dEGKWnyGKAU6UiR0M,24635
5
- unitlab/cloudflare_api_tunnel.py,sha256=XgDOQ-ISNDAJOlbKp96inGix3An_eBnAQ2pORcGBM40,14061
6
- unitlab/cloudflare_api_tunnel_backup.py,sha256=dG5Vax0JqrF2i-zxAFB-kNGyVSFR01-ovalwuJELqpo,28489
7
- unitlab/exceptions.py,sha256=68Tr6LreEzjQ3Vns8HAaWdtewtkNUJOvPazbf6NSnXU,950
8
- unitlab/main.py,sha256=7gPZ_2n90sxDnq9oGZVKOkuifr-k7w2Tq3ZIldAUE8I,5877
9
- unitlab/simple_tunnel.py,sha256=vA6OP2ogvDlRxXlu52acNcdgZYNMI2G-m2V7bv2kyrY,4867
10
- unitlab/tunnel_config.py,sha256=7CiAqasfg26YQfJYXapCBQPSoqw4jIx6yR64saybLLo,8312
11
- unitlab/tunnel_service_token.py,sha256=ji96a4s4W2cFJrHZle0zBD85Ac_T862-gCKzBUomrxM,3125
12
- unitlab/utils.py,sha256=83ekAxxfXecFTg76Z62BGDybC_skKJHYoLyawCD9wGM,1920
13
- unitlab-2.3.27.dist-info/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
14
- unitlab-2.3.27.dist-info/METADATA,sha256=A7IdVROWEy8jsfuER5nXt83nMOw-W8X7ZSgLeQyCHy0,844
15
- unitlab-2.3.27.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
16
- unitlab-2.3.27.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
17
- unitlab-2.3.27.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
18
- unitlab-2.3.27.dist-info/RECORD,,