suxitunnel 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/.gitignore +4 -0
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/PKG-INFO +61 -5
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/README.md +60 -4
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/__init__.py +1 -1
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/pyproject.toml +8 -1
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/suxitunnel.py +117 -25
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/LICENSE +0 -0
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/examples/basic_example.py +0 -0
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/examples/context_manager.py +0 -0
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/examples/fastapi_example.py +0 -0
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/examples/flask_example.py +0 -0
- {suxitunnel-0.1.1 → suxitunnel-0.1.3}/examples/multiple_tunnels.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: suxitunnel
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A simple Python library to expose local servers using Cloudflare Tunnel
|
|
5
5
|
Project-URL: Homepage, https://github.com/sadwx/suxitunnel
|
|
6
6
|
Project-URL: Repository, https://github.com/sadwx/suxitunnel
|
|
@@ -35,6 +35,21 @@ pip install suxitunnel
|
|
|
35
35
|
|
|
36
36
|
## Quick Start
|
|
37
37
|
|
|
38
|
+
### Command Line
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Expose local port 8000 (random URL)
|
|
42
|
+
suxitunnel --port 8000
|
|
43
|
+
|
|
44
|
+
# Or use short form
|
|
45
|
+
suxitunnel -p 8000
|
|
46
|
+
|
|
47
|
+
# Use a named tunnel with dedicated URL (requires Cloudflare account)
|
|
48
|
+
suxitunnel --port 8000 --tunnel-token YOUR_TOKEN
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Python API
|
|
52
|
+
|
|
38
53
|
```python
|
|
39
54
|
from suxitunnel import Tunnel
|
|
40
55
|
|
|
@@ -57,6 +72,8 @@ That's it! No configuration, no authentication, no manual setup.
|
|
|
57
72
|
- 🌍 **Global CDN** - Fast access from anywhere in the world
|
|
58
73
|
- 📦 **Auto-install** - Automatically downloads cloudflared if needed
|
|
59
74
|
- 🐍 **Simple API** - Clean, Pythonic interface
|
|
75
|
+
- 💻 **CLI Support** - Use from command line or as a library
|
|
76
|
+
- 🏷️ **Named Tunnels** - Support for dedicated URLs with tunnel tokens
|
|
60
77
|
|
|
61
78
|
## Usage
|
|
62
79
|
|
|
@@ -162,7 +179,31 @@ tunnel = Tunnel(8000, auto_download=False)
|
|
|
162
179
|
|
|
163
180
|
## API Reference
|
|
164
181
|
|
|
165
|
-
###
|
|
182
|
+
### Command Line Interface
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
suxitunnel [-h] -p PORT [--tunnel-token TUNNEL_TOKEN] [--local-https]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Arguments:**
|
|
189
|
+
- `-p, --port PORT`: Local port to expose (required)
|
|
190
|
+
- `--tunnel-token TOKEN`: Cloudflare tunnel token for named tunnels (optional)
|
|
191
|
+
- `--local-https`: Connect to local server via HTTPS (for self-signed certs)
|
|
192
|
+
- `-h, --help`: Show help message
|
|
193
|
+
|
|
194
|
+
**Examples:**
|
|
195
|
+
```bash
|
|
196
|
+
# Quick tunnel with random URL
|
|
197
|
+
suxitunnel --port 8000
|
|
198
|
+
|
|
199
|
+
# Named tunnel with dedicated URL
|
|
200
|
+
suxitunnel --port 8000 --tunnel-token eyJhIjoiNz...
|
|
201
|
+
|
|
202
|
+
# Local HTTPS server (e.g., .NET Core with HTTPS)
|
|
203
|
+
suxitunnel --port 5001 --local-https
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### `Tunnel(port, host="localhost", cloudflared_path=None, auto_download=True, tunnel_token=None, local_https=False)`
|
|
166
207
|
|
|
167
208
|
Create a tunnel to expose a local port.
|
|
168
209
|
|
|
@@ -171,19 +212,28 @@ Create a tunnel to expose a local port.
|
|
|
171
212
|
- `host` (str): Local hostname (default: "localhost")
|
|
172
213
|
- `cloudflared_path` (str): Path to cloudflared binary (auto-detected if None)
|
|
173
214
|
- `auto_download` (bool): Auto-download cloudflared if not found (default: True)
|
|
215
|
+
- `tunnel_token` (str): Cloudflare tunnel token for named tunnels (optional)
|
|
216
|
+
- `local_https` (bool): Set to True if local server uses HTTPS (default: False)
|
|
174
217
|
|
|
175
218
|
**Attributes:**
|
|
176
219
|
- `url` (str): Public HTTPS URL for the tunnel
|
|
177
220
|
- `port` (int): Local port being exposed
|
|
178
221
|
- `host` (str): Local hostname
|
|
222
|
+
- `tunnel_token` (str): Tunnel token if using named tunnel
|
|
179
223
|
- `is_alive()` (bool): Check if tunnel is running
|
|
180
224
|
- `close()`: Close the tunnel
|
|
181
225
|
|
|
182
226
|
**Example:**
|
|
183
227
|
```python
|
|
228
|
+
# Quick tunnel (random URL)
|
|
184
229
|
tunnel = Tunnel(8000)
|
|
185
230
|
print(tunnel.url) # https://abc123.trycloudflare.com
|
|
186
231
|
tunnel.close()
|
|
232
|
+
|
|
233
|
+
# Named tunnel (dedicated URL)
|
|
234
|
+
tunnel = Tunnel(8000, tunnel_token="your-token")
|
|
235
|
+
print(tunnel.url) # https://your-configured-domain.com
|
|
236
|
+
tunnel.close()
|
|
187
237
|
```
|
|
188
238
|
|
|
189
239
|
### `open_tunnel(port, host="localhost")`
|
|
@@ -216,7 +266,7 @@ Your App (localhost:8000) → cloudflared → Cloudflare Edge → Internet
|
|
|
216
266
|
|
|
217
267
|
### Cloudflared Binary
|
|
218
268
|
|
|
219
|
-
The library automatically downloads the `cloudflared` binary on first use if it's not already installed. The binary is saved to
|
|
269
|
+
The library automatically downloads the `cloudflared` binary on first use if it's not already installed. The binary is saved to the library's installation directory.
|
|
220
270
|
|
|
221
271
|
Supported platforms:
|
|
222
272
|
- Linux (x64, ARM64)
|
|
@@ -237,12 +287,18 @@ Supported platforms:
|
|
|
237
287
|
|
|
238
288
|
## Limitations
|
|
239
289
|
|
|
240
|
-
|
|
290
|
+
**Quick Tunnels (default):**
|
|
291
|
+
1. **Random URLs**: Each tunnel gets a random `*.trycloudflare.com` URL
|
|
241
292
|
2. **Temporary**: Tunnels are meant for development/testing, not production
|
|
242
293
|
3. **No persistence**: URL changes each time you create a new tunnel
|
|
243
294
|
4. **Rate limits**: Subject to Cloudflare's fair use policy
|
|
244
295
|
|
|
245
|
-
|
|
296
|
+
**Named Tunnels (with `--tunnel-token`):**
|
|
297
|
+
- Requires a free Cloudflare account
|
|
298
|
+
- Provides persistent, dedicated URLs
|
|
299
|
+
- Better suited for staging/production environments
|
|
300
|
+
|
|
301
|
+
For setting up named tunnels, see the [Cloudflare Tunnel documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/).
|
|
246
302
|
|
|
247
303
|
## Security Considerations
|
|
248
304
|
|
|
@@ -12,6 +12,21 @@ pip install suxitunnel
|
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
+
### Command Line
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Expose local port 8000 (random URL)
|
|
19
|
+
suxitunnel --port 8000
|
|
20
|
+
|
|
21
|
+
# Or use short form
|
|
22
|
+
suxitunnel -p 8000
|
|
23
|
+
|
|
24
|
+
# Use a named tunnel with dedicated URL (requires Cloudflare account)
|
|
25
|
+
suxitunnel --port 8000 --tunnel-token YOUR_TOKEN
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Python API
|
|
29
|
+
|
|
15
30
|
```python
|
|
16
31
|
from suxitunnel import Tunnel
|
|
17
32
|
|
|
@@ -34,6 +49,8 @@ That's it! No configuration, no authentication, no manual setup.
|
|
|
34
49
|
- 🌍 **Global CDN** - Fast access from anywhere in the world
|
|
35
50
|
- 📦 **Auto-install** - Automatically downloads cloudflared if needed
|
|
36
51
|
- 🐍 **Simple API** - Clean, Pythonic interface
|
|
52
|
+
- 💻 **CLI Support** - Use from command line or as a library
|
|
53
|
+
- 🏷️ **Named Tunnels** - Support for dedicated URLs with tunnel tokens
|
|
37
54
|
|
|
38
55
|
## Usage
|
|
39
56
|
|
|
@@ -139,7 +156,31 @@ tunnel = Tunnel(8000, auto_download=False)
|
|
|
139
156
|
|
|
140
157
|
## API Reference
|
|
141
158
|
|
|
142
|
-
###
|
|
159
|
+
### Command Line Interface
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
suxitunnel [-h] -p PORT [--tunnel-token TUNNEL_TOKEN] [--local-https]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Arguments:**
|
|
166
|
+
- `-p, --port PORT`: Local port to expose (required)
|
|
167
|
+
- `--tunnel-token TOKEN`: Cloudflare tunnel token for named tunnels (optional)
|
|
168
|
+
- `--local-https`: Connect to local server via HTTPS (for self-signed certs)
|
|
169
|
+
- `-h, --help`: Show help message
|
|
170
|
+
|
|
171
|
+
**Examples:**
|
|
172
|
+
```bash
|
|
173
|
+
# Quick tunnel with random URL
|
|
174
|
+
suxitunnel --port 8000
|
|
175
|
+
|
|
176
|
+
# Named tunnel with dedicated URL
|
|
177
|
+
suxitunnel --port 8000 --tunnel-token eyJhIjoiNz...
|
|
178
|
+
|
|
179
|
+
# Local HTTPS server (e.g., .NET Core with HTTPS)
|
|
180
|
+
suxitunnel --port 5001 --local-https
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `Tunnel(port, host="localhost", cloudflared_path=None, auto_download=True, tunnel_token=None, local_https=False)`
|
|
143
184
|
|
|
144
185
|
Create a tunnel to expose a local port.
|
|
145
186
|
|
|
@@ -148,19 +189,28 @@ Create a tunnel to expose a local port.
|
|
|
148
189
|
- `host` (str): Local hostname (default: "localhost")
|
|
149
190
|
- `cloudflared_path` (str): Path to cloudflared binary (auto-detected if None)
|
|
150
191
|
- `auto_download` (bool): Auto-download cloudflared if not found (default: True)
|
|
192
|
+
- `tunnel_token` (str): Cloudflare tunnel token for named tunnels (optional)
|
|
193
|
+
- `local_https` (bool): Set to True if local server uses HTTPS (default: False)
|
|
151
194
|
|
|
152
195
|
**Attributes:**
|
|
153
196
|
- `url` (str): Public HTTPS URL for the tunnel
|
|
154
197
|
- `port` (int): Local port being exposed
|
|
155
198
|
- `host` (str): Local hostname
|
|
199
|
+
- `tunnel_token` (str): Tunnel token if using named tunnel
|
|
156
200
|
- `is_alive()` (bool): Check if tunnel is running
|
|
157
201
|
- `close()`: Close the tunnel
|
|
158
202
|
|
|
159
203
|
**Example:**
|
|
160
204
|
```python
|
|
205
|
+
# Quick tunnel (random URL)
|
|
161
206
|
tunnel = Tunnel(8000)
|
|
162
207
|
print(tunnel.url) # https://abc123.trycloudflare.com
|
|
163
208
|
tunnel.close()
|
|
209
|
+
|
|
210
|
+
# Named tunnel (dedicated URL)
|
|
211
|
+
tunnel = Tunnel(8000, tunnel_token="your-token")
|
|
212
|
+
print(tunnel.url) # https://your-configured-domain.com
|
|
213
|
+
tunnel.close()
|
|
164
214
|
```
|
|
165
215
|
|
|
166
216
|
### `open_tunnel(port, host="localhost")`
|
|
@@ -193,7 +243,7 @@ Your App (localhost:8000) → cloudflared → Cloudflare Edge → Internet
|
|
|
193
243
|
|
|
194
244
|
### Cloudflared Binary
|
|
195
245
|
|
|
196
|
-
The library automatically downloads the `cloudflared` binary on first use if it's not already installed. The binary is saved to
|
|
246
|
+
The library automatically downloads the `cloudflared` binary on first use if it's not already installed. The binary is saved to the library's installation directory.
|
|
197
247
|
|
|
198
248
|
Supported platforms:
|
|
199
249
|
- Linux (x64, ARM64)
|
|
@@ -214,12 +264,18 @@ Supported platforms:
|
|
|
214
264
|
|
|
215
265
|
## Limitations
|
|
216
266
|
|
|
217
|
-
|
|
267
|
+
**Quick Tunnels (default):**
|
|
268
|
+
1. **Random URLs**: Each tunnel gets a random `*.trycloudflare.com` URL
|
|
218
269
|
2. **Temporary**: Tunnels are meant for development/testing, not production
|
|
219
270
|
3. **No persistence**: URL changes each time you create a new tunnel
|
|
220
271
|
4. **Rate limits**: Subject to Cloudflare's fair use policy
|
|
221
272
|
|
|
222
|
-
|
|
273
|
+
**Named Tunnels (with `--tunnel-token`):**
|
|
274
|
+
- Requires a free Cloudflare account
|
|
275
|
+
- Provides persistent, dedicated URLs
|
|
276
|
+
- Better suited for staging/production environments
|
|
277
|
+
|
|
278
|
+
For setting up named tunnels, see the [Cloudflare Tunnel documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/).
|
|
223
279
|
|
|
224
280
|
## Security Considerations
|
|
225
281
|
|
|
@@ -3,7 +3,7 @@ suxitunnel
|
|
|
3
3
|
A simple Python library to expose local servers using Cloudflare Tunnel
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from suxitunnel import Tunnel, TunnelError, open_tunnel
|
|
7
7
|
|
|
8
8
|
__version__ = "0.1.0"
|
|
9
9
|
__all__ = ["Tunnel", "TunnelError", "open_tunnel"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "suxitunnel"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "A simple Python library to expose local servers using Cloudflare Tunnel"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -22,6 +22,9 @@ dependencies = [
|
|
|
22
22
|
"requests>=2.32.0",
|
|
23
23
|
]
|
|
24
24
|
|
|
25
|
+
[project.scripts]
|
|
26
|
+
suxitunnel = "suxitunnel:main"
|
|
27
|
+
|
|
25
28
|
[project.urls]
|
|
26
29
|
Homepage = "https://github.com/sadwx/suxitunnel"
|
|
27
30
|
Repository = "https://github.com/sadwx/suxitunnel"
|
|
@@ -47,5 +50,9 @@ include = [
|
|
|
47
50
|
[dependency-groups]
|
|
48
51
|
dev = [
|
|
49
52
|
"build>=1.4.0",
|
|
53
|
+
"fastapi>=0.128.0",
|
|
54
|
+
"flask>=3.1.2",
|
|
55
|
+
"pytest>=9.0.2",
|
|
50
56
|
"twine>=6.2.0",
|
|
57
|
+
"uvicorn>=0.40.0",
|
|
51
58
|
]
|
|
@@ -51,19 +51,25 @@ class Tunnel:
|
|
|
51
51
|
port: int,
|
|
52
52
|
host: str = "localhost",
|
|
53
53
|
cloudflared_path: Optional[str] = None,
|
|
54
|
-
auto_download: bool = True
|
|
54
|
+
auto_download: bool = True,
|
|
55
|
+
tunnel_token: Optional[str] = None,
|
|
56
|
+
local_https: bool = False
|
|
55
57
|
):
|
|
56
58
|
"""
|
|
57
59
|
Create a tunnel to expose a local port.
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
Args:
|
|
60
62
|
port: Local port to expose (e.g., 8000)
|
|
61
63
|
host: Local host (default: "localhost")
|
|
62
64
|
cloudflared_path: Path to cloudflared binary (auto-detected if None)
|
|
63
65
|
auto_download: Automatically download cloudflared if not found
|
|
66
|
+
tunnel_token: Cloudflare tunnel token for named tunnels (optional)
|
|
67
|
+
local_https: Set to True if local server uses HTTPS (default: False)
|
|
64
68
|
"""
|
|
65
69
|
self.port = port
|
|
66
70
|
self.host = host
|
|
71
|
+
self.tunnel_token = tunnel_token
|
|
72
|
+
self.local_https = local_https
|
|
67
73
|
self.url: Optional[str] = None
|
|
68
74
|
self._process: Optional[subprocess.Popen] = None
|
|
69
75
|
self._ready = threading.Event()
|
|
@@ -86,8 +92,20 @@ class Tunnel:
|
|
|
86
92
|
# Start the tunnel
|
|
87
93
|
self._start()
|
|
88
94
|
|
|
95
|
+
def _get_library_dir(self) -> Path:
|
|
96
|
+
"""Get the directory where the library is located"""
|
|
97
|
+
return Path(__file__).parent
|
|
98
|
+
|
|
89
99
|
def _find_cloudflared(self) -> Optional[str]:
|
|
90
100
|
"""Try to find cloudflared in common locations"""
|
|
101
|
+
system = platform.system().lower()
|
|
102
|
+
binary_name = "cloudflared.exe" if system == "windows" else "cloudflared"
|
|
103
|
+
|
|
104
|
+
# First check the library directory (where downloaded binaries are stored)
|
|
105
|
+
lib_dir_path = self._get_library_dir() / binary_name
|
|
106
|
+
if lib_dir_path.exists():
|
|
107
|
+
return str(lib_dir_path)
|
|
108
|
+
|
|
91
109
|
# Check if in PATH
|
|
92
110
|
try:
|
|
93
111
|
result = subprocess.run(
|
|
@@ -99,29 +117,31 @@ class Tunnel:
|
|
|
99
117
|
return "cloudflared"
|
|
100
118
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
101
119
|
pass
|
|
102
|
-
|
|
120
|
+
|
|
103
121
|
# Check common install locations
|
|
104
122
|
possible_paths = [
|
|
105
123
|
"/usr/local/bin/cloudflared",
|
|
106
124
|
"/usr/bin/cloudflared",
|
|
107
125
|
str(Path.home() / "bin" / "cloudflared"),
|
|
108
126
|
str(Path.home() / ".local" / "bin" / "cloudflared"),
|
|
127
|
+
# Legacy download location
|
|
128
|
+
str(Path.home() / ".cloudflare-tunnel" / binary_name),
|
|
109
129
|
]
|
|
110
|
-
|
|
130
|
+
|
|
111
131
|
for path in possible_paths:
|
|
112
132
|
if Path(path).exists():
|
|
113
133
|
return path
|
|
114
|
-
|
|
134
|
+
|
|
115
135
|
return None
|
|
116
136
|
|
|
117
137
|
def _download_cloudflared(self) -> str:
|
|
118
138
|
"""Download cloudflared binary for the current platform"""
|
|
119
139
|
system = platform.system().lower()
|
|
120
140
|
machine = platform.machine().lower()
|
|
121
|
-
|
|
141
|
+
|
|
122
142
|
# Determine download URL
|
|
123
143
|
base_url = "https://github.com/cloudflare/cloudflared/releases/latest/download"
|
|
124
|
-
|
|
144
|
+
|
|
125
145
|
if system == "darwin": # macOS
|
|
126
146
|
if "arm" in machine or "aarch64" in machine:
|
|
127
147
|
filename = "cloudflared-darwin-arm64.tgz"
|
|
@@ -136,13 +156,12 @@ class Tunnel:
|
|
|
136
156
|
filename = "cloudflared-windows-amd64.exe"
|
|
137
157
|
else:
|
|
138
158
|
raise TunnelError(f"Unsupported platform: {system} {machine}")
|
|
139
|
-
|
|
159
|
+
|
|
140
160
|
download_url = f"{base_url}/{filename}"
|
|
141
|
-
|
|
142
|
-
# Download
|
|
143
|
-
download_dir =
|
|
144
|
-
|
|
145
|
-
|
|
161
|
+
|
|
162
|
+
# Download to the library directory
|
|
163
|
+
download_dir = self._get_library_dir()
|
|
164
|
+
|
|
146
165
|
if filename.endswith(".tgz"):
|
|
147
166
|
archive_path = download_dir / filename
|
|
148
167
|
binary_path = download_dir / "cloudflared"
|
|
@@ -150,7 +169,7 @@ class Tunnel:
|
|
|
150
169
|
binary_path = download_dir / "cloudflared.exe"
|
|
151
170
|
else:
|
|
152
171
|
binary_path = download_dir / "cloudflared"
|
|
153
|
-
|
|
172
|
+
|
|
154
173
|
# Check if already downloaded
|
|
155
174
|
if binary_path.exists():
|
|
156
175
|
return str(binary_path)
|
|
@@ -190,14 +209,29 @@ class Tunnel:
|
|
|
190
209
|
|
|
191
210
|
def _start(self):
|
|
192
211
|
"""Start the cloudflared tunnel"""
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
212
|
+
protocol = "https" if self.local_https else "http"
|
|
213
|
+
local_url = f"{protocol}://{self.host}:{self.port}"
|
|
214
|
+
|
|
215
|
+
if self.tunnel_token:
|
|
216
|
+
# Named tunnel with token
|
|
217
|
+
cmd = [
|
|
218
|
+
self.cloudflared_path,
|
|
219
|
+
"tunnel",
|
|
220
|
+
"--url", local_url,
|
|
221
|
+
"run",
|
|
222
|
+
"--token", self.tunnel_token
|
|
223
|
+
]
|
|
224
|
+
else:
|
|
225
|
+
# Quick tunnel (no authentication required)
|
|
226
|
+
cmd = [
|
|
227
|
+
self.cloudflared_path,
|
|
228
|
+
"tunnel",
|
|
229
|
+
"--url", local_url
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
# Add --no-tls-verify for local HTTPS servers with self-signed certs
|
|
233
|
+
if self.local_https:
|
|
234
|
+
cmd.append("--no-tls-verify")
|
|
201
235
|
|
|
202
236
|
try:
|
|
203
237
|
self._process = subprocess.Popen(
|
|
@@ -278,19 +312,77 @@ class Tunnel:
|
|
|
278
312
|
return f"<Tunnel port={self.port} url={self.url} status={status}>"
|
|
279
313
|
|
|
280
314
|
|
|
315
|
+
def parse_args(args: list[str] | None = None):
|
|
316
|
+
"""Parse command line arguments for the CLI."""
|
|
317
|
+
import argparse
|
|
318
|
+
|
|
319
|
+
parser = argparse.ArgumentParser(
|
|
320
|
+
prog="suxitunnel",
|
|
321
|
+
description="Expose local servers via Cloudflare Tunnel"
|
|
322
|
+
)
|
|
323
|
+
parser.add_argument(
|
|
324
|
+
"-p", "--port",
|
|
325
|
+
type=int,
|
|
326
|
+
required=True,
|
|
327
|
+
help="Local port to expose"
|
|
328
|
+
)
|
|
329
|
+
parser.add_argument(
|
|
330
|
+
"--tunnel-token",
|
|
331
|
+
type=str,
|
|
332
|
+
default=None,
|
|
333
|
+
help="Cloudflare tunnel token for named tunnels (optional)"
|
|
334
|
+
)
|
|
335
|
+
parser.add_argument(
|
|
336
|
+
"--local-https",
|
|
337
|
+
action="store_true",
|
|
338
|
+
default=False,
|
|
339
|
+
help="Use HTTPS to connect to local server (for servers with self-signed certs)"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
return parser.parse_args(args)
|
|
343
|
+
|
|
344
|
+
|
|
281
345
|
def open_tunnel(port: int, host: str = "localhost") -> Tunnel:
|
|
282
346
|
"""
|
|
283
347
|
Convenience function to create a tunnel.
|
|
284
|
-
|
|
348
|
+
|
|
285
349
|
Args:
|
|
286
350
|
port: Local port to expose
|
|
287
351
|
host: Local host (default: "localhost")
|
|
288
|
-
|
|
352
|
+
|
|
289
353
|
Returns:
|
|
290
354
|
Tunnel instance
|
|
291
|
-
|
|
355
|
+
|
|
292
356
|
Example:
|
|
293
357
|
tunnel = open_tunnel(8000)
|
|
294
358
|
print(f"Public URL: {tunnel.url}")
|
|
295
359
|
"""
|
|
296
360
|
return Tunnel(port=port, host=host)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def main(args: list[str] | None = None) -> None:
|
|
364
|
+
"""CLI entry point for suxitunnel."""
|
|
365
|
+
parsed = parse_args(args)
|
|
366
|
+
|
|
367
|
+
tunnel = Tunnel(
|
|
368
|
+
port=parsed.port,
|
|
369
|
+
tunnel_token=parsed.tunnel_token,
|
|
370
|
+
local_https=parsed.local_https
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
print(f"Tunnel URL: {tunnel.url}")
|
|
374
|
+
print("Press Ctrl+C to stop the tunnel")
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
while tunnel.is_alive():
|
|
378
|
+
time.sleep(1)
|
|
379
|
+
except KeyboardInterrupt:
|
|
380
|
+
pass
|
|
381
|
+
finally:
|
|
382
|
+
tunnel.close()
|
|
383
|
+
|
|
384
|
+
raise SystemExit(0)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
if __name__ == "__main__":
|
|
388
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|