globalhost 0.1.0__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.
- globalhost/__init__.py +4 -0
- globalhost/core.py +73 -0
- globalhost/tunnel.py +101 -0
- globalhost-0.1.0.dist-info/METADATA +106 -0
- globalhost-0.1.0.dist-info/RECORD +7 -0
- globalhost-0.1.0.dist-info/WHEEL +5 -0
- globalhost-0.1.0.dist-info/top_level.txt +1 -0
globalhost/__init__.py
ADDED
globalhost/core.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import logging
|
|
5
|
+
from .tunnel import TunnelManager
|
|
6
|
+
|
|
7
|
+
# Root logger configuration for the globalhost package
|
|
8
|
+
logger = logging.getLogger("globalhost")
|
|
9
|
+
|
|
10
|
+
class GlobalHostApp:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.tunnel = TunnelManager()
|
|
13
|
+
self.url = None
|
|
14
|
+
|
|
15
|
+
def launch(self, framework: str, app: str, port: int = 8000):
|
|
16
|
+
framework = framework.lower()
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
# 1. Start tunnel and grab URL
|
|
20
|
+
self.url = self.tunnel.start(port=port)
|
|
21
|
+
|
|
22
|
+
logger.info(f"\nš GLOBALHOST IS LIVE!\nš YOUR PUBLIC URL: {self.url} š\n")
|
|
23
|
+
|
|
24
|
+
# 2. Spin up framework
|
|
25
|
+
if framework == "fastapi":
|
|
26
|
+
logger.info(f"Launching FastAPI backend via Uvicorn on port {port}...")
|
|
27
|
+
|
|
28
|
+
# Get the directory of the currently running script
|
|
29
|
+
script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
30
|
+
|
|
31
|
+
subprocess.run(
|
|
32
|
+
[sys.executable, "-m", "uvicorn", app, "--host", "127.0.0.1", "--port", str(port)],
|
|
33
|
+
cwd=script_dir # š Run Uvicorn from the script's directory
|
|
34
|
+
)
|
|
35
|
+
elif framework == "django":
|
|
36
|
+
logger.info(f"Launching Django server on port {port}...")
|
|
37
|
+
|
|
38
|
+
if os.path.exists("manage.py"):
|
|
39
|
+
subprocess.run([sys.executable, "manage.py", "runserver", f"127.0.0.1:{port}"])
|
|
40
|
+
else:
|
|
41
|
+
logger.info("No manage.py detected. Booting Django in standalone mode...")
|
|
42
|
+
|
|
43
|
+
# Clean up file paths or standard dot notation signatures
|
|
44
|
+
clean_app = app.replace("\\", "/").rstrip(".py")
|
|
45
|
+
|
|
46
|
+
if "/" in clean_app:
|
|
47
|
+
module_dir = os.path.dirname(os.path.abspath(clean_app))
|
|
48
|
+
module_name = os.path.basename(clean_app)
|
|
49
|
+
else:
|
|
50
|
+
module_dir = os.getcwd()
|
|
51
|
+
module_name = clean_app
|
|
52
|
+
|
|
53
|
+
# Inject the target example directory directly into the python process environment map
|
|
54
|
+
env = os.environ.copy()
|
|
55
|
+
env["PYTHONPATH"] = module_dir + os.pathsep + env.get("PYTHONPATH", "")
|
|
56
|
+
|
|
57
|
+
subprocess.run([
|
|
58
|
+
sys.executable, "-m", "django",
|
|
59
|
+
"runserver", f"127.0.0.1:{port}",
|
|
60
|
+
f"--settings={module_name}"
|
|
61
|
+
], env=env)
|
|
62
|
+
else:
|
|
63
|
+
logger.error(f"Unknown framework '{framework}'")
|
|
64
|
+
self.tunnel.stop()
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
except KeyboardInterrupt:
|
|
68
|
+
self.tunnel.stop()
|
|
69
|
+
logger.info("GlobalHost safely shut down.")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
self.tunnel.stop()
|
|
72
|
+
logger.error(f"GlobalHost unexpected crash: {e}")
|
|
73
|
+
sys.exit(1)
|
globalhost/tunnel.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
import sys
|
|
6
|
+
import urllib.request
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
# Set up a named logger for this module
|
|
10
|
+
logger = logging.getLogger("globalhost.tunnel")
|
|
11
|
+
|
|
12
|
+
class TunnelManager:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.system = platform.system().lower()
|
|
15
|
+
self.machine = platform.machine().lower()
|
|
16
|
+
self.process = None
|
|
17
|
+
self.public_url = None
|
|
18
|
+
|
|
19
|
+
def _get_binary_config(self):
|
|
20
|
+
base_url = "https://github.com/cloudflare/cloudflared/releases/latest/download/"
|
|
21
|
+
if "windows" in self.system:
|
|
22
|
+
filename = "cloudflared-windows-amd64.exe"
|
|
23
|
+
return filename, base_url + filename
|
|
24
|
+
elif "linux" in self.system:
|
|
25
|
+
filename = "cloudflared-linux-arm64" if "arm" in self.machine or "aarch64" in self.machine else "cloudflared-linux-amd64"
|
|
26
|
+
return filename, base_url + filename
|
|
27
|
+
elif "darwin" in self.system:
|
|
28
|
+
filename = "cloudflared-darwin-arm64.tgz" if "arm" in self.machine else "cloudflared-darwin-amd64.tgz"
|
|
29
|
+
return filename, base_url + filename
|
|
30
|
+
else:
|
|
31
|
+
raise RuntimeError(f"GlobalHost doesn't support your OS yet: {self.system}")
|
|
32
|
+
|
|
33
|
+
def _ensure_binary_exists(self) -> str:
|
|
34
|
+
base_dir = os.path.dirname(__file__)
|
|
35
|
+
bin_dir = os.path.join(base_dir, "bin")
|
|
36
|
+
os.makedirs(bin_dir, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
filename, download_url = self._get_binary_config()
|
|
39
|
+
binary_path = os.path.join(bin_dir, filename)
|
|
40
|
+
|
|
41
|
+
if filename.endswith(".tgz"):
|
|
42
|
+
binary_path = os.path.join(bin_dir, "cloudflared")
|
|
43
|
+
|
|
44
|
+
if not os.path.exists(binary_path):
|
|
45
|
+
logger.info("Core networking module missing. Downloading from Cloudflare network...")
|
|
46
|
+
logger.debug(f"Downloading from URL: {download_url}")
|
|
47
|
+
try:
|
|
48
|
+
temp_download = os.path.join(bin_dir, filename)
|
|
49
|
+
urllib.request.urlretrieve(download_url, temp_download)
|
|
50
|
+
if filename.endswith(".tgz"):
|
|
51
|
+
import tarfile
|
|
52
|
+
with tarfile.open(temp_download, "r:gz") as tar:
|
|
53
|
+
tar.extractall(path=bin_dir)
|
|
54
|
+
os.remove(temp_download)
|
|
55
|
+
logger.info("Download complete.")
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Failed to download network binary: {e}")
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
if "windows" not in self.system:
|
|
61
|
+
os.chmod(binary_path, 0o755)
|
|
62
|
+
|
|
63
|
+
return binary_path
|
|
64
|
+
|
|
65
|
+
def start(self, port: int) -> str:
|
|
66
|
+
binary = self._ensure_binary_exists()
|
|
67
|
+
|
|
68
|
+
logger.info(f"Establishing secure edge network tunnel to port {port}...")
|
|
69
|
+
|
|
70
|
+
self.process = subprocess.Popen(
|
|
71
|
+
[binary, "tunnel", "--url", f"http://localhost:{port}"],
|
|
72
|
+
stdout=subprocess.PIPE,
|
|
73
|
+
stderr=subprocess.STDOUT,
|
|
74
|
+
text=True,
|
|
75
|
+
bufsize=1
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
start_time = time.time()
|
|
79
|
+
while time.time() - start_time < 10:
|
|
80
|
+
line = self.process.stdout.readline()
|
|
81
|
+
if not line:
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
if ".trycloudflare.com" in line:
|
|
85
|
+
for word in line.split():
|
|
86
|
+
if "trycloudflare.com" in word:
|
|
87
|
+
url = word.strip()
|
|
88
|
+
if url.startswith("https://"):
|
|
89
|
+
self.public_url = url
|
|
90
|
+
else:
|
|
91
|
+
self.public_url = f"https://{url}"
|
|
92
|
+
break
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
return self.public_url
|
|
96
|
+
|
|
97
|
+
def stop(self):
|
|
98
|
+
if self.process:
|
|
99
|
+
logger.info("Collapsing tunnel, removing machine from public routing...")
|
|
100
|
+
self.process.terminate()
|
|
101
|
+
self.process.wait()
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: globalhost
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Turn any machine into a public server instantly with automated zero-config tunnels.
|
|
5
|
+
Author-email: Joel Opoku <joelclouds@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/joelclouds/globalhost
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
Requires-Dist: uvicorn>=0.22.0
|
|
13
|
+
|
|
14
|
+
# GlobalHost š°ļø
|
|
15
|
+
|
|
16
|
+
Turn any machine running your code into a publicly accessible, secure SaaS backend instantly. No `localhost`, no router configuration, and zero cloud deployment setup needed during development.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
* **Zero-Config Public Ingress:** Automatically spawns a secure, temporary public HTTPS tunnel (`.trycloudflare.com`) on startup.
|
|
20
|
+
* **Framework Agnostic:** Works flawlessly out-of-the-box with **FastAPI**, **Django**, Flask, or raw Python scripts.
|
|
21
|
+
* **Self-Contained & Lightweight:** Auto-provisions the exact native binary for your OS (Mac/Win/Lin) on the fly. No `brew`, `apt`, or manual downloads required, and keeps the pip package tiny.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Architecture
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
[ Any PC Running Your Code ] āā(Outbound Tunnel)āā> [ Global Edge Network ] āā> [ Public Internet URL ]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### 1. Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install globalhost
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Basic Usage (Zero-Config)
|
|
42
|
+
|
|
43
|
+
Drop this into your project entrypoint. This automatically boots the tunnel and wraps your web framework.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from globalhost import GlobalHostApp
|
|
47
|
+
from fastapi import FastAPI
|
|
48
|
+
|
|
49
|
+
app = FastAPI()
|
|
50
|
+
|
|
51
|
+
@app.get("/my-algo")
|
|
52
|
+
def run_algo():
|
|
53
|
+
return {"status": "Your golden algo is live globally!"}
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
# Boots tunnel and starts the server
|
|
57
|
+
GlobalHostApp.launch(framework="fastapi", app="main:app", port=8000)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. Programmatic Usage (Advanced)
|
|
61
|
+
|
|
62
|
+
Need to inject the live public URL into a database, webhook, or frontend config at runtime? Use the programmatic API:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from globalhost import GlobalHostApp
|
|
66
|
+
|
|
67
|
+
gh = GlobalHostApp()
|
|
68
|
+
|
|
69
|
+
# Start the tunnel and grab the endpoint string
|
|
70
|
+
public_endpoint = gh.tunnel.start(port=9000)
|
|
71
|
+
|
|
72
|
+
print(f"š Live Hyperlink: {public_endpoint}")
|
|
73
|
+
print(f"š¦ Stored Attribute: {gh.tunnel.public_url}")
|
|
74
|
+
|
|
75
|
+
# Keep tunnel alive while your app runs...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## How It Works Under the Hood
|
|
81
|
+
|
|
82
|
+
1. **OS Detection:** On execution, the module identifies the hosting machine's operating system and architecture.
|
|
83
|
+
2. **Auto-Provisioning:** It silently downloads and caches the exact lightweight network binary required for that specific system.
|
|
84
|
+
3. **Reverse Tunnel Routing:** It executes the binary to establish a secure outbound connection to a global edge router, bypassing local firewalls and printing your public production-ready endpoint straight to the console.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
Ready-to-run implementations are available in the `examples/` directory:
|
|
91
|
+
* **FastAPI:** `examples/fastapi/01_json_api.py` & `02_webpage.py`
|
|
92
|
+
* **Django:** `examples/django/01_json_api.py` & `02_webpage.py`
|
|
93
|
+
* **Runtime URL Extraction:** `examples/runtime_url.py`
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Author
|
|
98
|
+
|
|
99
|
+
**Joel Opoku**
|
|
100
|
+
š§ [joelclouds@gmail.com](mailto:joelclouds@gmail.com)
|
|
101
|
+
š [joelclouds.pythonanywhere.com](https://joelclouds.pythonanywhere.com)
|
|
102
|
+
š [github.com/joelclouds/globalhost](https://github.com/joelclouds/globalhost)
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
globalhost/__init__.py,sha256=oTz_h5NEmfEtHDY66QWrSD7P71EMmMPf7LxiR1DLaWA,83
|
|
2
|
+
globalhost/core.py,sha256=A2mX7_5Vn-LxeyavJTRFkub8OL-Us4ssLrY11-E9pq4,2885
|
|
3
|
+
globalhost/tunnel.py,sha256=LZK_CpxDxMzp8UJKKj2UyNoSDV4lZGLoIaM6TZN0Nvk,3741
|
|
4
|
+
globalhost-0.1.0.dist-info/METADATA,sha256=A-Hr_JV_W5wDNXMO59T1BX6JCvHb739yPidwP1D9fUE,3470
|
|
5
|
+
globalhost-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
6
|
+
globalhost-0.1.0.dist-info/top_level.txt,sha256=c4InFFLegEgznGRj3zG1ksGe_1lYpWJb25ZsX_W4jOA,11
|
|
7
|
+
globalhost-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
globalhost
|