main
_py 157 lines 6.09 KB
Raw
1 import os
2 import platform
3 import requests
4 import subprocess
5 import threading
6 from helpers import files
7 from helpers.print_style import PrintStyle
8
9 class CloudflareTunnel:
10 def __init__(self, port: int):
11 self.port = port
12 self.bin_dir = "tmp" # Relative path
13 self.cloudflared_path = None
14 self.tunnel_process = None
15 self.tunnel_url = None
16 self._stop_event = threading.Event()
17
18 def download_cloudflared(self):
19 """Downloads the appropriate cloudflared binary for the current system"""
20 # Create bin directory if it doesn't exist using files helper
21 os.makedirs(files.get_abs_path(self.bin_dir), exist_ok=True)
22
23 # Determine OS and architecture
24 system = platform.system().lower()
25 arch = platform.machine().lower()
26
27 # Define executable name
28 executable_name = "cloudflared.exe" if system == "windows" else "cloudflared"
29 install_path = files.get_abs_path(self.bin_dir, executable_name)
30
31 # Return if already exists
32 if files.exists(self.bin_dir, executable_name):
33 self.cloudflared_path = install_path
34 return install_path
35
36 # Map platform/arch to download URLs
37 base_url = "https://github.com/cloudflare/cloudflared/releases/latest/download/"
38
39 if system == "darwin": # macOS
40 # Download and extract .tgz for macOS
41 download_file = "cloudflared-darwin-amd64.tgz" if arch == "x86_64" else "cloudflared-darwin-arm64.tgz"
42 download_url = f"{base_url}{download_file}"
43 download_path = files.get_abs_path(self.bin_dir, download_file)
44
45 PrintStyle().print(f"\nDownloading cloudflared from: {download_url}")
46 response = requests.get(download_url, stream=True)
47 if response.status_code != 200:
48 raise RuntimeError(f"Failed to download cloudflared: {response.status_code}")
49
50 # Save the .tgz file
51 with open(download_path, "wb") as f:
52 for chunk in response.iter_content(chunk_size=8192):
53 f.write(chunk)
54
55 # Extract cloudflared binary from .tgz
56 import tarfile
57 with tarfile.open(download_path, "r:gz") as tar:
58 tar.extract("cloudflared", files.get_abs_path(self.bin_dir))
59
60 # Cleanup .tgz file
61 os.remove(download_path)
62
63 else: # Linux and Windows
64 if system == "linux":
65 if arch in ["x86_64", "amd64"]:
66 download_file = "cloudflared-linux-amd64"
67 elif arch == "arm64" or arch == "aarch64":
68 download_file = "cloudflared-linux-arm64"
69 elif arch == "arm":
70 download_file = "cloudflared-linux-arm"
71 else:
72 download_file = "cloudflared-linux-386"
73 elif system == "windows":
74 download_file = "cloudflared-windows-amd64.exe"
75 else:
76 raise RuntimeError(f"Unsupported platform: {system} {arch}")
77
78 download_url = f"{base_url}{download_file}"
79 download_path = files.get_abs_path(self.bin_dir, download_file)
80
81 PrintStyle().print(f"\nDownloading cloudflared from: {download_url}")
82 response = requests.get(download_url, stream=True)
83 if response.status_code != 200:
84 raise RuntimeError(f"Failed to download cloudflared: {response.status_code}")
85
86 with open(download_path, "wb") as f:
87 for chunk in response.iter_content(chunk_size=8192):
88 f.write(chunk)
89
90
91 # Rename and set permissions
92 if os.path.exists(install_path):
93 os.remove(install_path)
94 os.rename(download_path, install_path)
95
96 # Set executable permissions
97 if system != "windows":
98 os.chmod(install_path, 0o755)
99
100 self.cloudflared_path = install_path
101 return install_path
102
103 def _extract_tunnel_url(self, process):
104 """Extracts the tunnel URL from cloudflared output"""
105 while not self._stop_event.is_set():
106 line = process.stdout.readline()
107 if not line:
108 break
109
110 if isinstance(line, bytes):
111 line = line.decode('utf-8')
112
113 if "trycloudflare.com" in line and "https://" in line:
114 start = line.find("https://")
115 end = line.find("trycloudflare.com") + len("trycloudflare.com")
116 self.tunnel_url = line[start:end].strip()
117 PrintStyle().print("\n=== Cloudflare Tunnel URL ===")
118 PrintStyle().print(f"URL: {self.tunnel_url}")
119 PrintStyle().print("============================\n")
120 return
121
122 def start(self):
123 """Starts Cloudflare Tunnel"""
124 if not self.cloudflared_path:
125 self.download_cloudflared()
126
127 PrintStyle().print("\nStarting Cloudflare Tunnel...")
128 # Start tunnel process
129 self.tunnel_process = subprocess.Popen(
130 [
131 str(self.cloudflared_path),
132 "tunnel",
133 "--url",
134 f"http://localhost:{self.port}"
135 ],
136 stdout=subprocess.PIPE,
137 stderr=subprocess.STDOUT,
138 bufsize=1,
139 universal_newlines=True
140 )
141
142 # Extract tunnel URL in separate thread
143 threading.Thread(
144 target=self._extract_tunnel_url,
145 args=(self.tunnel_process,),
146 daemon=True
147 ).start()
148
149 def stop(self):
150 """Stops Cloudflare Tunnel"""
151 self._stop_event.set()
152 if self.tunnel_process:
153 PrintStyle().print("\nStopping Cloudflare Tunnel...")
154 self.tunnel_process.terminate()
155 self.tunnel_process.wait()
156 self.tunnel_process = None
157 self.tunnel_url = None