unitlab 2.3.33__py3-none-any.whl → 2.3.35__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/client.py +78 -64
- unitlab/main.py +17 -32
- unitlab/persistent_tunnel.py +196 -51
- unitlab/utils.py +2 -0
- {unitlab-2.3.33.dist-info → unitlab-2.3.35.dist-info}/METADATA +12 -3
- unitlab-2.3.35.dist-info/RECORD +13 -0
- {unitlab-2.3.33.dist-info → unitlab-2.3.35.dist-info}/WHEEL +1 -1
- unitlab/api_tunnel.py +0 -238
- unitlab/auto_tunnel.py +0 -174
- unitlab/binary_manager.py +0 -154
- unitlab/cloudflare_api_tunnel.py +0 -379
- unitlab/cloudflare_api_tunnel_backup.py +0 -653
- unitlab/dynamic_tunnel.py +0 -272
- unitlab/easy_tunnel.py +0 -210
- unitlab/simple_tunnel.py +0 -205
- unitlab/tunnel_config.py +0 -204
- unitlab/tunnel_service_token.py +0 -104
- unitlab-2.3.33.dist-info/RECORD +0 -23
- {unitlab-2.3.33.dist-info → unitlab-2.3.35.dist-info}/entry_points.txt +0 -0
- {unitlab-2.3.33.dist-info → unitlab-2.3.35.dist-info/licenses}/LICENSE.md +0 -0
- {unitlab-2.3.33.dist-info → unitlab-2.3.35.dist-info}/top_level.txt +0 -0
unitlab/auto_tunnel.py
DELETED
@@ -1,174 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Automatic Tunnel Creation - Simplest approach using cloudflared's built-in quick tunnel
|
4
|
-
No API tokens needed!
|
5
|
-
"""
|
6
|
-
|
7
|
-
import subprocess
|
8
|
-
import time
|
9
|
-
import re
|
10
|
-
import os
|
11
|
-
|
12
|
-
class AutoTunnel:
|
13
|
-
def __init__(self, device_id=None):
|
14
|
-
"""
|
15
|
-
Initialize auto tunnel - no credentials needed!
|
16
|
-
"""
|
17
|
-
self.device_id = device_id or "device"
|
18
|
-
self.jupyter_process = None
|
19
|
-
self.tunnel_process = None
|
20
|
-
self.tunnel_url = None
|
21
|
-
|
22
|
-
def get_cloudflared_path(self):
|
23
|
-
"""Get or download cloudflared binary"""
|
24
|
-
import platform
|
25
|
-
|
26
|
-
# Check if exists in system
|
27
|
-
import shutil
|
28
|
-
if shutil.which("cloudflared"):
|
29
|
-
return "cloudflared"
|
30
|
-
|
31
|
-
# Check local
|
32
|
-
local_bin = os.path.expanduser("~/.local/bin/cloudflared")
|
33
|
-
if os.path.exists(local_bin):
|
34
|
-
return local_bin
|
35
|
-
|
36
|
-
# Download it
|
37
|
-
print("📦 Downloading cloudflared...")
|
38
|
-
system = platform.system().lower()
|
39
|
-
if system == "linux":
|
40
|
-
arch = "amd64" if "x86" in platform.machine() else "arm64"
|
41
|
-
url = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-{}".format(arch)
|
42
|
-
|
43
|
-
os.makedirs(os.path.expanduser("~/.local/bin"), exist_ok=True)
|
44
|
-
subprocess.run("curl -L {} -o {}".format(url, local_bin), shell=True, capture_output=True)
|
45
|
-
subprocess.run("chmod +x {}".format(local_bin), shell=True)
|
46
|
-
print("✅ cloudflared downloaded")
|
47
|
-
return local_bin
|
48
|
-
|
49
|
-
return "cloudflared"
|
50
|
-
|
51
|
-
def start_jupyter(self):
|
52
|
-
"""Start Jupyter notebook"""
|
53
|
-
print("🚀 Starting Jupyter on port 8888...")
|
54
|
-
|
55
|
-
cmd = [
|
56
|
-
"jupyter", "notebook",
|
57
|
-
"--port", "8888",
|
58
|
-
"--no-browser",
|
59
|
-
"--ip", "0.0.0.0",
|
60
|
-
"--NotebookApp.token=''",
|
61
|
-
"--NotebookApp.password=''",
|
62
|
-
"--NotebookApp.allow_origin='*'"
|
63
|
-
]
|
64
|
-
|
65
|
-
self.jupyter_process = subprocess.Popen(
|
66
|
-
cmd,
|
67
|
-
stdout=subprocess.PIPE,
|
68
|
-
stderr=subprocess.PIPE
|
69
|
-
)
|
70
|
-
|
71
|
-
time.sleep(3)
|
72
|
-
print("✅ Jupyter started")
|
73
|
-
return True
|
74
|
-
|
75
|
-
def start_tunnel(self):
|
76
|
-
"""Start tunnel using cloudflared quick tunnel - no auth needed!"""
|
77
|
-
print("🔧 Starting automatic tunnel (no credentials needed)...")
|
78
|
-
|
79
|
-
cloudflared = self.get_cloudflared_path()
|
80
|
-
|
81
|
-
# Use cloudflared's quick tunnel feature - generates random URL
|
82
|
-
cmd = [
|
83
|
-
cloudflared,
|
84
|
-
"tunnel",
|
85
|
-
"--url", "http://localhost:8888"
|
86
|
-
]
|
87
|
-
|
88
|
-
self.tunnel_process = subprocess.Popen(
|
89
|
-
cmd,
|
90
|
-
stdout=subprocess.PIPE,
|
91
|
-
stderr=subprocess.STDOUT,
|
92
|
-
text=True,
|
93
|
-
bufsize=1
|
94
|
-
)
|
95
|
-
|
96
|
-
# Read output to get the tunnel URL
|
97
|
-
print("⏳ Waiting for tunnel URL...")
|
98
|
-
for _ in range(30): # Wait up to 30 seconds
|
99
|
-
line = self.tunnel_process.stdout.readline()
|
100
|
-
if line:
|
101
|
-
# Look for the tunnel URL in output
|
102
|
-
match = re.search(r'https://[a-zA-Z0-9-]+\.trycloudflare\.com', line)
|
103
|
-
if match:
|
104
|
-
self.tunnel_url = match.group(0)
|
105
|
-
print("✅ Tunnel created: {}".format(self.tunnel_url))
|
106
|
-
return True
|
107
|
-
time.sleep(1)
|
108
|
-
|
109
|
-
print("❌ Failed to get tunnel URL")
|
110
|
-
return False
|
111
|
-
|
112
|
-
def start(self):
|
113
|
-
"""Start everything - super simple!"""
|
114
|
-
try:
|
115
|
-
print("="*50)
|
116
|
-
print("🌐 Automatic Cloudflare Tunnel (No Auth Needed!)")
|
117
|
-
print("Device: {}".format(self.device_id))
|
118
|
-
print("="*50)
|
119
|
-
|
120
|
-
# 1. Start Jupyter
|
121
|
-
if not self.start_jupyter():
|
122
|
-
raise Exception("Failed to start Jupyter")
|
123
|
-
|
124
|
-
# 2. Start tunnel (automatic, no credentials)
|
125
|
-
if not self.start_tunnel():
|
126
|
-
raise Exception("Failed to start tunnel")
|
127
|
-
|
128
|
-
print("\n" + "="*50)
|
129
|
-
print("🎉 SUCCESS! Your Jupyter is accessible at:")
|
130
|
-
print(" {}".format(self.tunnel_url))
|
131
|
-
print("="*50)
|
132
|
-
print("\n⚠️ Note: This URL is temporary and random")
|
133
|
-
print("For persistent URLs, use Cloudflare API approach")
|
134
|
-
|
135
|
-
return True
|
136
|
-
|
137
|
-
except Exception as e:
|
138
|
-
print("❌ Error: {}".format(e))
|
139
|
-
self.stop()
|
140
|
-
return False
|
141
|
-
|
142
|
-
def stop(self):
|
143
|
-
"""Stop everything"""
|
144
|
-
if self.jupyter_process:
|
145
|
-
self.jupyter_process.terminate()
|
146
|
-
if self.tunnel_process:
|
147
|
-
self.tunnel_process.terminate()
|
148
|
-
|
149
|
-
def run(self):
|
150
|
-
"""Run and keep alive"""
|
151
|
-
try:
|
152
|
-
if self.start():
|
153
|
-
print("\nPress Ctrl+C to stop...")
|
154
|
-
while True:
|
155
|
-
time.sleep(1)
|
156
|
-
except KeyboardInterrupt:
|
157
|
-
print("\n⏹️ Shutting down...")
|
158
|
-
self.stop()
|
159
|
-
print("👋 Goodbye!")
|
160
|
-
|
161
|
-
|
162
|
-
def main():
|
163
|
-
"""Test automatic tunnel"""
|
164
|
-
import platform
|
165
|
-
device_id = platform.node()
|
166
|
-
|
167
|
-
print("Starting auto tunnel for: {}".format(device_id))
|
168
|
-
|
169
|
-
tunnel = AutoTunnel(device_id=device_id)
|
170
|
-
tunnel.run()
|
171
|
-
|
172
|
-
|
173
|
-
if __name__ == "__main__":
|
174
|
-
main()
|
unitlab/binary_manager.py
DELETED
@@ -1,154 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import platform
|
3
|
-
import hashlib
|
4
|
-
import urllib.request
|
5
|
-
from pathlib import Path
|
6
|
-
import stat
|
7
|
-
import json
|
8
|
-
|
9
|
-
class CloudflaredBinaryManager:
|
10
|
-
"""
|
11
|
-
Manages cloudflared binary automatically
|
12
|
-
- Downloads on first use
|
13
|
-
- Caches for future use
|
14
|
-
- Verifies integrity
|
15
|
-
- Zero user configuration
|
16
|
-
"""
|
17
|
-
|
18
|
-
# Binary URLs and checksums
|
19
|
-
BINARIES = {
|
20
|
-
'linux-amd64': {
|
21
|
-
'url': 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64',
|
22
|
-
'checksum': 'sha256:...', # Add real checksums
|
23
|
-
'filename': 'cloudflared'
|
24
|
-
},
|
25
|
-
'linux-arm64': {
|
26
|
-
'url': 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64',
|
27
|
-
'checksum': 'sha256:...',
|
28
|
-
'filename': 'cloudflared'
|
29
|
-
},
|
30
|
-
'darwin-amd64': {
|
31
|
-
'url': 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz',
|
32
|
-
'checksum': 'sha256:...',
|
33
|
-
'filename': 'cloudflared',
|
34
|
-
'compressed': True
|
35
|
-
},
|
36
|
-
'windows-amd64': {
|
37
|
-
'url': 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe',
|
38
|
-
'checksum': 'sha256:...',
|
39
|
-
'filename': 'cloudflared.exe'
|
40
|
-
}
|
41
|
-
}
|
42
|
-
|
43
|
-
def __init__(self):
|
44
|
-
# User's home directory - works on all platforms
|
45
|
-
self.cache_dir = Path.home() / '.unitlab' / 'bin'
|
46
|
-
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
47
|
-
|
48
|
-
# Detect platform once
|
49
|
-
self.platform_key = self._detect_platform()
|
50
|
-
|
51
|
-
def _detect_platform(self):
|
52
|
-
"""Detect OS and architecture"""
|
53
|
-
system = platform.system().lower()
|
54
|
-
machine = platform.machine().lower()
|
55
|
-
|
56
|
-
if system == 'linux':
|
57
|
-
if machine in ['x86_64', 'amd64']:
|
58
|
-
return 'linux-amd64'
|
59
|
-
elif machine in ['aarch64', 'arm64']:
|
60
|
-
return 'linux-arm64'
|
61
|
-
|
62
|
-
elif system == 'darwin': # macOS
|
63
|
-
# Check if ARM (M1/M2) or Intel
|
64
|
-
if machine == 'arm64':
|
65
|
-
return 'darwin-arm64'
|
66
|
-
return 'darwin-amd64'
|
67
|
-
|
68
|
-
elif system == 'windows':
|
69
|
-
return 'windows-amd64'
|
70
|
-
|
71
|
-
raise RuntimeError(f"Unsupported platform: {system} {machine}")
|
72
|
-
|
73
|
-
def get_binary_path(self):
|
74
|
-
"""Get path to cloudflared binary, downloading if needed"""
|
75
|
-
|
76
|
-
binary_info = self.BINARIES[self.platform_key]
|
77
|
-
binary_path = self.cache_dir / binary_info['filename']
|
78
|
-
|
79
|
-
# Check if already downloaded
|
80
|
-
if binary_path.exists():
|
81
|
-
print("✓ Using cached cloudflared")
|
82
|
-
return str(binary_path)
|
83
|
-
|
84
|
-
# Download for first time
|
85
|
-
print("🔄 First time setup - downloading cloudflared...")
|
86
|
-
self._download_binary(binary_info, binary_path)
|
87
|
-
|
88
|
-
return str(binary_path)
|
89
|
-
|
90
|
-
def _download_binary(self, info, target_path):
|
91
|
-
"""Download and verify binary"""
|
92
|
-
|
93
|
-
# Create SSL context to handle certificate issues
|
94
|
-
import ssl
|
95
|
-
ssl_context = ssl.create_default_context()
|
96
|
-
ssl_context.check_hostname = False
|
97
|
-
ssl_context.verify_mode = ssl.CERT_NONE
|
98
|
-
|
99
|
-
# Download with progress bar
|
100
|
-
def download_progress(block_num, block_size, total_size):
|
101
|
-
downloaded = block_num * block_size
|
102
|
-
if total_size > 0:
|
103
|
-
percent = min(downloaded * 100 / total_size, 100)
|
104
|
-
print(f"Downloading: {percent:.0f}%", end='\r')
|
105
|
-
else:
|
106
|
-
print(f"Downloading: {downloaded} bytes", end='\r')
|
107
|
-
|
108
|
-
temp_file = target_path.with_suffix('.tmp')
|
109
|
-
|
110
|
-
try:
|
111
|
-
# Download file with SSL context
|
112
|
-
req = urllib.request.Request(info['url'], headers={'User-Agent': 'Mozilla/5.0'})
|
113
|
-
with urllib.request.urlopen(req, context=ssl_context) as response:
|
114
|
-
total_size = int(response.headers.get('Content-Length', 0))
|
115
|
-
|
116
|
-
with open(temp_file, 'wb') as f:
|
117
|
-
downloaded = 0
|
118
|
-
block_size = 8192
|
119
|
-
while True:
|
120
|
-
chunk = response.read(block_size)
|
121
|
-
if not chunk:
|
122
|
-
break
|
123
|
-
f.write(chunk)
|
124
|
-
downloaded += len(chunk)
|
125
|
-
if total_size > 0:
|
126
|
-
percent = min(downloaded * 100 / total_size, 100)
|
127
|
-
print(f"Downloading: {percent:.0f}%", end='\r')
|
128
|
-
print("\n✓ Download complete")
|
129
|
-
|
130
|
-
# Handle compressed files (macOS .tgz)
|
131
|
-
if info.get('compressed'):
|
132
|
-
import tarfile
|
133
|
-
with tarfile.open(temp_file, 'r:gz') as tar:
|
134
|
-
# Extract just the cloudflared binary
|
135
|
-
tar.extract('cloudflared', self.cache_dir)
|
136
|
-
temp_file.unlink()
|
137
|
-
else:
|
138
|
-
# Move to final location
|
139
|
-
temp_file.rename(target_path)
|
140
|
-
|
141
|
-
# Make executable on Unix systems
|
142
|
-
if platform.system() != 'Windows':
|
143
|
-
target_path.chmod(target_path.stat().st_mode | stat.S_IEXEC)
|
144
|
-
|
145
|
-
print("✓ Cloudflared ready!")
|
146
|
-
|
147
|
-
except Exception as e:
|
148
|
-
print(f"❌ Download failed: {e}")
|
149
|
-
if temp_file.exists():
|
150
|
-
temp_file.unlink()
|
151
|
-
raise
|
152
|
-
|
153
|
-
|
154
|
-
|