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/tunnel_config.py
DELETED
@@ -1,204 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Cloudflare Tunnel Configuration using Service Token
|
3
|
-
No user login required - uses pre-generated service token
|
4
|
-
"""
|
5
|
-
|
6
|
-
import os
|
7
|
-
import subprocess
|
8
|
-
import socket
|
9
|
-
import time
|
10
|
-
import logging
|
11
|
-
from .binary_manager import CloudflaredBinaryManager
|
12
|
-
|
13
|
-
logger = logging.getLogger(__name__)
|
14
|
-
|
15
|
-
|
16
|
-
class CloudflareTunnel:
|
17
|
-
def __init__(self, base_domain, device_id): # base_domain kept for compatibility
|
18
|
-
"""
|
19
|
-
Initialize tunnel with service token
|
20
|
-
No login or credential files needed
|
21
|
-
"""
|
22
|
-
# Configuration
|
23
|
-
self.base_domain = "1scan.uz" # Hardcoded domain
|
24
|
-
self.device_id = device_id
|
25
|
-
self.hostname = socket.gethostname()
|
26
|
-
|
27
|
-
# Initialize binary manager to handle cloudflared
|
28
|
-
self.binary_manager = CloudflaredBinaryManager()
|
29
|
-
|
30
|
-
# Service token - replace with your actual token from cloudflared tunnel token command
|
31
|
-
# This token can ONLY run the tunnel, cannot modify or delete it
|
32
|
-
# To generate: cloudflared tunnel token [tunnel-name]
|
33
|
-
self.service_token = os.getenv(
|
34
|
-
"CLOUDFLARE_TUNNEL_TOKEN",
|
35
|
-
"eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJ0IjoiMDc3N2ZjMTAtNDljNC00NzJkLTg2NjEtZjYwZDgwZDYxODRkIiwicyI6Ik9XRTNaak5tTVdVdE1tWTRaUzAwTmpoakxUazBaalF0WXpjek1tSm1ZVGt4WlRRMCJ9" # TODO: Replace with your new tunnel's token
|
36
|
-
)
|
37
|
-
|
38
|
-
if self.service_token == "YOUR_SERVICE_TOKEN_HERE":
|
39
|
-
logger.warning(
|
40
|
-
"⚠️ No service token configured. "
|
41
|
-
"Set CLOUDFLARE_TUNNEL_TOKEN env var or update the token in tunnel_config.py"
|
42
|
-
)
|
43
|
-
|
44
|
-
# Use single subdomain per device with service differentiation
|
45
|
-
# This works with *.1scan.uz wildcard certificate
|
46
|
-
self.device_subdomain = f"{device_id.replace('-', '').replace('_', '').lower()[:20]}"
|
47
|
-
|
48
|
-
# Both services use same subdomain
|
49
|
-
self.jupyter_url = f"https://{self.device_subdomain}.{self.base_domain}"
|
50
|
-
self.ssh_hostname = f"{self.device_subdomain}.{self.base_domain}" # For SSH ProxyCommand
|
51
|
-
self.ssh_url = self.ssh_hostname # Keep for backward compatibility
|
52
|
-
|
53
|
-
self.tunnel_process = None
|
54
|
-
|
55
|
-
def check_cloudflared_installed(self):
|
56
|
-
"""Check if cloudflared is installed"""
|
57
|
-
try:
|
58
|
-
result = subprocess.run(
|
59
|
-
["cloudflared", "--version"],
|
60
|
-
capture_output=True,
|
61
|
-
text=True
|
62
|
-
)
|
63
|
-
return result.returncode == 0
|
64
|
-
except FileNotFoundError:
|
65
|
-
return False
|
66
|
-
|
67
|
-
def install_cloudflared(self):
|
68
|
-
"""Auto-install cloudflared if not present"""
|
69
|
-
import platform
|
70
|
-
|
71
|
-
system = platform.system().lower()
|
72
|
-
machine = platform.machine().lower()
|
73
|
-
|
74
|
-
print("📦 Installing cloudflared...")
|
75
|
-
|
76
|
-
try:
|
77
|
-
if system == "linux":
|
78
|
-
# Determine architecture
|
79
|
-
if machine in ["x86_64", "amd64"]:
|
80
|
-
arch = "amd64"
|
81
|
-
elif machine in ["aarch64", "arm64"]:
|
82
|
-
arch = "arm64"
|
83
|
-
else:
|
84
|
-
arch = "386"
|
85
|
-
|
86
|
-
# Download and install
|
87
|
-
url = f"https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-{arch}"
|
88
|
-
|
89
|
-
commands = [
|
90
|
-
f"curl -L {url} -o /tmp/cloudflared",
|
91
|
-
"chmod +x /tmp/cloudflared",
|
92
|
-
"sudo mv /tmp/cloudflared /usr/local/bin/cloudflared 2>/dev/null || "
|
93
|
-
"mkdir -p ~/.local/bin && mv /tmp/cloudflared ~/.local/bin/cloudflared"
|
94
|
-
]
|
95
|
-
|
96
|
-
for cmd in commands:
|
97
|
-
subprocess.run(cmd, shell=True, check=False)
|
98
|
-
|
99
|
-
# Add ~/.local/bin to PATH if needed
|
100
|
-
local_bin = os.path.expanduser("~/.local/bin")
|
101
|
-
if local_bin not in os.environ.get("PATH", ""):
|
102
|
-
os.environ["PATH"] = f"{local_bin}:{os.environ['PATH']}"
|
103
|
-
|
104
|
-
return self.check_cloudflared_installed()
|
105
|
-
|
106
|
-
elif system == "darwin":
|
107
|
-
# Try homebrew first
|
108
|
-
result = subprocess.run(["brew", "install", "cloudflared"], capture_output=True)
|
109
|
-
if result.returncode != 0:
|
110
|
-
# Fallback to direct download
|
111
|
-
url = "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz"
|
112
|
-
subprocess.run(f"curl -L {url} | tar xz", shell=True)
|
113
|
-
subprocess.run("sudo mv cloudflared /usr/local/bin/", shell=True)
|
114
|
-
|
115
|
-
return self.check_cloudflared_installed()
|
116
|
-
|
117
|
-
elif system == "windows":
|
118
|
-
print("⚠️ Please install cloudflared manually on Windows")
|
119
|
-
print(" Download from: https://github.com/cloudflare/cloudflared/releases")
|
120
|
-
return False
|
121
|
-
|
122
|
-
except Exception as e:
|
123
|
-
logger.error(f"Failed to install cloudflared: {e}")
|
124
|
-
return False
|
125
|
-
|
126
|
-
def setup(self, jupyter_port): # jupyter_port kept for compatibility
|
127
|
-
"""
|
128
|
-
Setup and start tunnel with service token
|
129
|
-
No login required! Binary is automatically downloaded if needed.
|
130
|
-
"""
|
131
|
-
print("🚀 Setting up Cloudflare tunnel with service token...")
|
132
|
-
|
133
|
-
# Binary manager will automatically download cloudflared if needed
|
134
|
-
# No manual installation required!
|
135
|
-
|
136
|
-
# Start tunnel with service token
|
137
|
-
return self.start_tunnel_with_token()
|
138
|
-
|
139
|
-
def start_tunnel_with_token(self):
|
140
|
-
"""
|
141
|
-
Start the tunnel using service token
|
142
|
-
Binary is automatically downloaded if needed
|
143
|
-
"""
|
144
|
-
try:
|
145
|
-
print("🚀 Starting Cloudflare tunnel...")
|
146
|
-
|
147
|
-
# Get cloudflared binary path (downloads if needed)
|
148
|
-
cloudflared_path = self.binary_manager.get_binary_path()
|
149
|
-
print(f"📍 Using cloudflared at: {cloudflared_path}")
|
150
|
-
|
151
|
-
# Simple command with service token
|
152
|
-
cmd = [
|
153
|
-
cloudflared_path,
|
154
|
-
"tunnel",
|
155
|
-
"--no-autoupdate", # Prevent auto-updates during run
|
156
|
-
"run",
|
157
|
-
"--token",
|
158
|
-
self.service_token
|
159
|
-
]
|
160
|
-
|
161
|
-
# Start the tunnel process
|
162
|
-
self.tunnel_process = subprocess.Popen(
|
163
|
-
cmd,
|
164
|
-
stdout=subprocess.PIPE,
|
165
|
-
stderr=subprocess.STDOUT,
|
166
|
-
text=True,
|
167
|
-
bufsize=1
|
168
|
-
)
|
169
|
-
|
170
|
-
# Wait for tunnel to establish
|
171
|
-
print("⏳ Waiting for tunnel to connect...")
|
172
|
-
time.sleep(5)
|
173
|
-
|
174
|
-
# Check if process is still running
|
175
|
-
if self.tunnel_process.poll() is None:
|
176
|
-
print("✅ Tunnel is running!")
|
177
|
-
print(f"📌 Device subdomain: {self.device_subdomain}.{self.base_domain}")
|
178
|
-
print(f"📌 Jupyter URL: {self.jupyter_url}")
|
179
|
-
print(f"📌 SSH access: ssh -o ProxyCommand='cloudflared access ssh --hostname {self.ssh_hostname}' user@localhost")
|
180
|
-
return self.tunnel_process
|
181
|
-
else:
|
182
|
-
# Read any error output
|
183
|
-
output = self.tunnel_process.stdout.read()
|
184
|
-
print("❌ Tunnel failed to start")
|
185
|
-
print(f"Error output: {output}")
|
186
|
-
return None
|
187
|
-
|
188
|
-
except Exception as e:
|
189
|
-
print(f"❌ Error starting tunnel: {e}")
|
190
|
-
return None
|
191
|
-
|
192
|
-
def stop(self):
|
193
|
-
"""Stop the tunnel if running"""
|
194
|
-
if self.tunnel_process and self.tunnel_process.poll() is None:
|
195
|
-
print("Stopping tunnel...")
|
196
|
-
self.tunnel_process.terminate()
|
197
|
-
self.tunnel_process.wait(timeout=5)
|
198
|
-
print("Tunnel stopped")
|
199
|
-
|
200
|
-
# Removed all the old methods that are no longer needed:
|
201
|
-
# - login() - not needed with service token
|
202
|
-
# - create_tunnel() - tunnel already exists
|
203
|
-
# - configure_dns() - already configured
|
204
|
-
# - create_config_file() - not needed with service token
|
unitlab/tunnel_service_token.py
DELETED
@@ -1,104 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Service Token implementation for Cloudflare Tunnel
|
3
|
-
More secure than embedding full credentials
|
4
|
-
"""
|
5
|
-
|
6
|
-
import os
|
7
|
-
import subprocess
|
8
|
-
import time
|
9
|
-
from pathlib import Path
|
10
|
-
|
11
|
-
|
12
|
-
class ServiceTokenTunnel:
|
13
|
-
"""
|
14
|
-
Use Cloudflare Service Token instead of credentials file
|
15
|
-
This is more secure and doesn't require login
|
16
|
-
"""
|
17
|
-
|
18
|
-
# Embed the service token (generated once by admin)
|
19
|
-
# This token can ONLY run the tunnel, cannot modify it
|
20
|
-
DEFAULT_SERVICE_TOKEN = "eyJhIjoiYzkxMTkyYWUyMGE1ZDQzZjY1ZTA4NzU1MGQ4ZGM4OWIiLCJzIjoiZmdnSHowbFJFRnBHa05TZzIzV3JKMVBiaDVROGVUd0oyYWtJWThXdjhtTT0iLCJ0IjoiYjMzZGFhOGYtMmNjMy00Y2FkLWEyMjgtOTdlMDYwNzBlNjAwIn0="
|
21
|
-
|
22
|
-
def __init__(self, device_id, service_token=None):
|
23
|
-
self.device_id = device_id
|
24
|
-
# Allow override via environment variable or parameter
|
25
|
-
self.service_token = (
|
26
|
-
service_token or
|
27
|
-
os.getenv("CLOUDFLARE_TUNNEL_TOKEN") or
|
28
|
-
self.DEFAULT_SERVICE_TOKEN
|
29
|
-
)
|
30
|
-
|
31
|
-
# With service token, we don't need credential files
|
32
|
-
# The token contains all necessary information
|
33
|
-
|
34
|
-
def start_tunnel_with_token(self):
|
35
|
-
"""
|
36
|
-
Start tunnel using service token
|
37
|
-
No login, no credential files needed
|
38
|
-
"""
|
39
|
-
print("🚀 Starting tunnel with service token...")
|
40
|
-
|
41
|
-
# Service token method - super simple!
|
42
|
-
cmd = [
|
43
|
-
"cloudflared",
|
44
|
-
"tunnel",
|
45
|
-
"run",
|
46
|
-
"--token",
|
47
|
-
self.service_token
|
48
|
-
]
|
49
|
-
|
50
|
-
try:
|
51
|
-
# Start the tunnel
|
52
|
-
process = subprocess.Popen(
|
53
|
-
cmd,
|
54
|
-
stdout=subprocess.PIPE,
|
55
|
-
stderr=subprocess.PIPE,
|
56
|
-
text=True
|
57
|
-
)
|
58
|
-
|
59
|
-
# Wait for tunnel to establish
|
60
|
-
time.sleep(3)
|
61
|
-
|
62
|
-
if process.poll() is None:
|
63
|
-
print("✅ Tunnel running with service token")
|
64
|
-
return process
|
65
|
-
else:
|
66
|
-
print("❌ Failed to start tunnel")
|
67
|
-
return None
|
68
|
-
|
69
|
-
except Exception as e:
|
70
|
-
print(f"❌ Error: {e}")
|
71
|
-
return None
|
72
|
-
|
73
|
-
def get_tunnel_info(self):
|
74
|
-
"""
|
75
|
-
Service tokens use predetermined URLs
|
76
|
-
The admin sets these up when creating the tunnel
|
77
|
-
"""
|
78
|
-
# These URLs are configured when creating the tunnel
|
79
|
-
base_domain = "1scan.uz"
|
80
|
-
return {
|
81
|
-
"jupyter_url": f"https://jupyter-{self.device_id}.{base_domain}",
|
82
|
-
"ssh_url": f"https://ssh-{self.device_id}.{base_domain}"
|
83
|
-
}
|
84
|
-
|
85
|
-
|
86
|
-
# Usage example
|
87
|
-
def run_with_service_token(device_id):
|
88
|
-
"""
|
89
|
-
Example of how simple it is with service token
|
90
|
-
"""
|
91
|
-
tunnel = ServiceTokenTunnel(device_id)
|
92
|
-
|
93
|
-
# No login needed!
|
94
|
-
# No credential files needed!
|
95
|
-
# Just run with the token
|
96
|
-
process = tunnel.start_tunnel_with_token()
|
97
|
-
|
98
|
-
if process:
|
99
|
-
urls = tunnel.get_tunnel_info()
|
100
|
-
print(f"Jupyter: {urls['jupyter_url']}")
|
101
|
-
print(f"SSH: {urls['ssh_url']}")
|
102
|
-
return process
|
103
|
-
|
104
|
-
return None
|
unitlab-2.3.33.dist-info/RECORD
DELETED
@@ -1,23 +0,0 @@
|
|
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=ftiW_ZHCHiKUdCizGq1lsq2YnOCGjjqKm9E8vM9dHbg,25636
|
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=ARgToqC63NPhSxtRxarhPT0P2LgH09_pZW0Xj8mFAVY,5366
|
13
|
-
unitlab/persistent_tunnel.py,sha256=usi7lAFC_Qhmmh1hTTmLHXlm5-EUiUXFgVDm4uXJqjw,15214
|
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.33.dist-info/LICENSE.md,sha256=Gn7RRvByorAcAaM-WbyUpsgi5ED1-bKFFshbWfYYz2Y,1069
|
19
|
-
unitlab-2.3.33.dist-info/METADATA,sha256=zx2NFeFbXhodlUQcmG-1-B0YFFis8DCUTw62L-CeJcY,844
|
20
|
-
unitlab-2.3.33.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
21
|
-
unitlab-2.3.33.dist-info/entry_points.txt,sha256=ig-PjKEqSCj3UTdyANgEi4tsAU84DyXdaOJ02NHX4bY,45
|
22
|
-
unitlab-2.3.33.dist-info/top_level.txt,sha256=Al4ZlTYE3fTJK2o6YLCDMH5_DjuQkffRBMxgmWbKaqQ,8
|
23
|
-
unitlab-2.3.33.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|