prosoon-pf 0.1.0__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.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: prosoon-pf
3
+ Version: 0.1.0
4
+ Summary: Quick-start a local HTTP server and optionally expose it via a Cloudflare tunnel.
5
+ Author: Prasun Banerjee
6
+ License: MIT
7
+ Keywords: http,server,cloudflare,tunnel,share,cli
8
+ Classifier: Environment :: Console
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+
15
+ # prosoon-pf
16
+
17
+ Quick-start a local HTTP server, and optionally expose it to the internet with a
18
+ Cloudflare quick tunnel — in one command. Linux only for now.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install prosoon-pf
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Serve the current directory locally on port 8000:
29
+
30
+ ```bash
31
+ prosoon-pf
32
+ ```
33
+
34
+ Pick a port and a directory:
35
+
36
+ ```bash
37
+ prosoon-pf 9000 --dir ~/share
38
+ ```
39
+
40
+ Expose it publicly (prints a `https://….trycloudflare.com` link):
41
+
42
+ ```bash
43
+ prosoon-pf 8000 --public
44
+ ```
45
+
46
+ If `cloudflared` isn't installed, `--public` mode will ask before installing it.
47
+
48
+ ## Public mode is public
49
+
50
+ When you use `--public`, **every file in the served directory becomes
51
+ downloadable by anyone with the link** for as long as the command runs. The tool
52
+ shows you the directory contents and asks for confirmation first. Don't serve a
53
+ folder that contains passwords, keys, `.ovpn` files, or anything private.
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,43 @@
1
+ # prosoon-pf
2
+
3
+ Quick-start a local HTTP server, and optionally expose it to the internet with a
4
+ Cloudflare quick tunnel — in one command. Linux only for now.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pip install prosoon-pf
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ Serve the current directory locally on port 8000:
15
+
16
+ ```bash
17
+ prosoon-pf
18
+ ```
19
+
20
+ Pick a port and a directory:
21
+
22
+ ```bash
23
+ prosoon-pf 9000 --dir ~/share
24
+ ```
25
+
26
+ Expose it publicly (prints a `https://….trycloudflare.com` link):
27
+
28
+ ```bash
29
+ prosoon-pf 8000 --public
30
+ ```
31
+
32
+ If `cloudflared` isn't installed, `--public` mode will ask before installing it.
33
+
34
+ ## Public mode is public
35
+
36
+ When you use `--public`, **every file in the served directory becomes
37
+ downloadable by anyone with the link** for as long as the command runs. The tool
38
+ shows you the directory contents and asks for confirmation first. Don't serve a
39
+ folder that contains passwords, keys, `.ovpn` files, or anything private.
40
+
41
+ ## License
42
+
43
+ MIT
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "prosoon-pf"
7
+ version = "0.1.0"
8
+ description = "Quick-start a local HTTP server and optionally expose it via a Cloudflare tunnel."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Prasun Banerjee" }]
13
+ keywords = ["http", "server", "cloudflare", "tunnel", "share", "cli"]
14
+ classifiers = [
15
+ "Environment :: Console",
16
+ "Operating System :: POSIX :: Linux",
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ ]
20
+
21
+ [project.urls]
22
+ # Homepage = "N/A"
23
+
24
+ # This is what makes `prosoon-pf` a runnable command after install.
25
+ # Format: command-name = "import.path:function"
26
+ [project.scripts]
27
+ prosoon-pf = "prosoon_pf.cli:main"
28
+
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """prosoon-pf: quick-start a local HTTP server and optionally expose it via a Cloudflare tunnel."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,240 @@
1
+ """
2
+ prosoon-pf — quick-start an HTTP server and optionally expose it publicly.
3
+
4
+ What it automates (the commands you'd otherwise type by hand):
5
+ 1. python3 -m http.server <port>
6
+ 2. ensure cloudflared is installed (check -> ask consent -> install)
7
+ 3. cloudflared tunnel --url http://localhost:<port>
8
+
9
+ Design notes for future-you:
10
+ - We use subprocess to run the real programs rather than reimplementing
11
+ them. http.server and cloudflared already exist and are battle-tested;
12
+ our job is just orchestration.
13
+ - We never run a privileged install without asking. Auto-sudo from a pip
14
+ package is exactly what makes a package look malicious.
15
+ """
16
+
17
+ import argparse
18
+ import http.server
19
+ import os
20
+ import re
21
+ import shutil
22
+ import signal
23
+ import socketserver
24
+ import subprocess
25
+ import sys
26
+ import threading
27
+ from pathlib import Path
28
+
29
+ from . import __version__
30
+
31
+
32
+ # ----------------------------------------------------------------------------
33
+ # Step 0: environment checks
34
+ # ----------------------------------------------------------------------------
35
+
36
+ def check_python_version(minimum=(3, 8)):
37
+ """Refuse to run on ancient Python. We rely on f-strings and pathlib,
38
+ so anything below 3.8 is not worth supporting."""
39
+ if sys.version_info < minimum:
40
+ need = ".".join(str(n) for n in minimum)
41
+ have = ".".join(str(n) for n in sys.version_info[:3])
42
+ sys.exit(f"[!] Python {need}+ required, but you're on {have}.")
43
+
44
+
45
+ def cloudflared_path():
46
+ """Return the path to cloudflared if it's on PATH, else None.
47
+ shutil.which is the Python equivalent of the shell `which` command."""
48
+ return shutil.which("cloudflared")
49
+
50
+
51
+ # ----------------------------------------------------------------------------
52
+ # Step 1: the local server
53
+ # ----------------------------------------------------------------------------
54
+
55
+ def serve_directory(port, directory):
56
+ """Start http.server bound to the given port, serving `directory`.
57
+
58
+ We run this in-process (not via subprocess) so we control the lifecycle
59
+ cleanly and can shut it down on Ctrl+C. ThreadingHTTPServer handles
60
+ multiple requests without blocking, which matters when a browser opens
61
+ several connections at once.
62
+ """
63
+ directory = str(Path(directory).resolve())
64
+
65
+ # A handler factory that serves from `directory` instead of cwd.
66
+ def handler(*args, **kwargs):
67
+ return http.server.SimpleHTTPRequestHandler(
68
+ *args, directory=directory, **kwargs
69
+ )
70
+
71
+ # allow_reuse_address avoids "Address already in use" on quick restarts.
72
+ socketserver.ThreadingTCPServer.allow_reuse_address = True
73
+ httpd = socketserver.ThreadingTCPServer(("0.0.0.0", port), handler)
74
+
75
+ # Run the blocking serve loop on a background thread so main() can
76
+ # continue (e.g. to start the tunnel) and still catch Ctrl+C.
77
+ thread = threading.Thread(target=httpd.serve_forever, daemon=True)
78
+ thread.start()
79
+ return httpd
80
+
81
+
82
+ # ----------------------------------------------------------------------------
83
+ # Step 2: ensure cloudflared exists
84
+ # ----------------------------------------------------------------------------
85
+
86
+ INSTALL_HINT = (
87
+ "Install cloudflared manually, then re-run:\n"
88
+ " wget https://github.com/cloudflare/cloudflared/releases/latest/"
89
+ "download/cloudflared-linux-amd64.deb\n"
90
+ " sudo dpkg -i cloudflared-linux-amd64.deb"
91
+ )
92
+
93
+
94
+ def ensure_cloudflared(assume_yes=False):
95
+ """Return a path to cloudflared, installing it (with consent) if missing.
96
+
97
+ Linux-only for now. We ask before doing anything that needs sudo.
98
+ """
99
+ existing = cloudflared_path()
100
+ if existing:
101
+ return existing
102
+
103
+ print("[!] cloudflared is not installed.")
104
+ if not assume_yes:
105
+ answer = input(" Install it now? (needs sudo) [y/N]: ").strip().lower()
106
+ if answer not in ("y", "yes"):
107
+ sys.exit("[!] Cannot create a public tunnel without cloudflared.\n" + INSTALL_HINT)
108
+
109
+ arch = os.uname().machine
110
+ # Map uname arch -> the .deb Cloudflare publishes.
111
+ deb_arch = {"x86_64": "amd64", "aarch64": "arm64", "armv7l": "arm"}.get(arch)
112
+ if deb_arch is None:
113
+ sys.exit(f"[!] Unsupported architecture: {arch}.\n" + INSTALL_HINT)
114
+
115
+ url = (
116
+ f"https://github.com/cloudflare/cloudflared/releases/latest/"
117
+ f"download/cloudflared-linux-{deb_arch}.deb"
118
+ )
119
+ deb = f"/tmp/cloudflared-{deb_arch}.deb"
120
+
121
+ try:
122
+ print(f"[*] Downloading {url}")
123
+ subprocess.run(["wget", "-qO", deb, url], check=True)
124
+ print("[*] Installing (you may be prompted for your sudo password)...")
125
+ subprocess.run(["sudo", "dpkg", "-i", deb], check=True)
126
+ except subprocess.CalledProcessError as exc:
127
+ sys.exit(f"[!] Install failed ({exc}).\n" + INSTALL_HINT)
128
+ finally:
129
+ # Clean up the downloaded .deb regardless of outcome.
130
+ try:
131
+ os.remove(deb)
132
+ except OSError:
133
+ pass
134
+
135
+ path = cloudflared_path()
136
+ if not path:
137
+ sys.exit("[!] cloudflared still not found after install.\n" + INSTALL_HINT)
138
+ return path
139
+
140
+
141
+ # ----------------------------------------------------------------------------
142
+ # Step 3: the tunnel
143
+ # ----------------------------------------------------------------------------
144
+
145
+ TUNNEL_URL_RE = re.compile(r"https://[-\w]+\.trycloudflare\.com")
146
+
147
+
148
+ def start_tunnel(cloudflared, port):
149
+ """Launch a cloudflared quick tunnel and stream its output, printing the
150
+ public URL prominently when it appears.
151
+
152
+ cloudflared writes its status (including the URL) to stderr, so we merge
153
+ stderr into stdout and read line by line.
154
+ """
155
+ proc = subprocess.Popen(
156
+ [cloudflared, "tunnel", "--url", f"http://localhost:{port}"],
157
+ stdout=subprocess.PIPE,
158
+ stderr=subprocess.STDOUT,
159
+ text=True,
160
+ bufsize=1,
161
+ )
162
+
163
+ printed = False
164
+ for line in proc.stdout:
165
+ match = TUNNEL_URL_RE.search(line)
166
+ if match and not printed:
167
+ url = match.group(0)
168
+ print("\n" + "=" * 56)
169
+ print(f" Public URL: {url}")
170
+ print("=" * 56 + "\n")
171
+ printed = True
172
+ # Uncomment to see raw cloudflared logs:
173
+ # print(line, end="")
174
+ return proc
175
+
176
+
177
+ # ----------------------------------------------------------------------------
178
+ # Wiring it together
179
+ # ----------------------------------------------------------------------------
180
+
181
+ def main(argv=None):
182
+ parser = argparse.ArgumentParser(
183
+ prog="prosoon-pf",
184
+ description="Quick-start a local HTTP server, optionally exposed via Cloudflare.",
185
+ )
186
+ parser.add_argument("port", nargs="?", type=int, default=8000,
187
+ help="Port to serve on (default: 8000).")
188
+ parser.add_argument("-d", "--dir", default=".",
189
+ help="Directory to serve (default: current dir).")
190
+ parser.add_argument("--public", action="store_true",
191
+ help="Expose the server publicly via a Cloudflare tunnel.")
192
+ parser.add_argument("-y", "--yes", action="store_true",
193
+ help="Skip the cloudflared install confirmation.")
194
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
195
+ args = parser.parse_args(argv)
196
+
197
+ check_python_version()
198
+
199
+ serve_dir = Path(args.dir).resolve()
200
+
201
+ # The safety confirmation for public mode. This is the whole reason the
202
+ # tool is trustworthy: the user sees exactly what they're about to expose.
203
+ if args.public:
204
+ print(f"[!] PUBLIC mode: everything in {serve_dir} will be reachable")
205
+ print(" by anyone with the link, for as long as this runs.")
206
+ try:
207
+ entries = sorted(p.name for p in serve_dir.iterdir())
208
+ except OSError:
209
+ entries = []
210
+ if entries:
211
+ preview = ", ".join(entries[:8]) + (" ..." if len(entries) > 8 else "")
212
+ print(f" Contents: {preview}")
213
+ if not args.yes:
214
+ confirm = input(" Expose this directory publicly? [y/N]: ").strip().lower()
215
+ if confirm not in ("y", "yes"):
216
+ sys.exit("[*] Aborted. Nothing was exposed.")
217
+
218
+ httpd = serve_directory(args.port, serve_dir)
219
+ print(f"[*] Serving {serve_dir} at http://localhost:{args.port}")
220
+
221
+ tunnel_proc = None
222
+ try:
223
+ if args.public:
224
+ cf = ensure_cloudflared(assume_yes=args.yes)
225
+ print("[*] Starting Cloudflare tunnel...")
226
+ tunnel_proc = start_tunnel(cf, args.port) # blocks, streaming logs
227
+ else:
228
+ print("[*] Local mode. Press Ctrl+C to stop.")
229
+ signal.pause() # sleep until a signal (Ctrl+C) arrives
230
+ except KeyboardInterrupt:
231
+ print("\n[*] Shutting down...")
232
+ finally:
233
+ if tunnel_proc and tunnel_proc.poll() is None:
234
+ tunnel_proc.terminate()
235
+ httpd.shutdown()
236
+ print("[*] Stopped.")
237
+
238
+
239
+ if __name__ == "__main__":
240
+ main()
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: prosoon-pf
3
+ Version: 0.1.0
4
+ Summary: Quick-start a local HTTP server and optionally expose it via a Cloudflare tunnel.
5
+ Author: Prasun Banerjee
6
+ License: MIT
7
+ Keywords: http,server,cloudflare,tunnel,share,cli
8
+ Classifier: Environment :: Console
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+
15
+ # prosoon-pf
16
+
17
+ Quick-start a local HTTP server, and optionally expose it to the internet with a
18
+ Cloudflare quick tunnel — in one command. Linux only for now.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install prosoon-pf
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ Serve the current directory locally on port 8000:
29
+
30
+ ```bash
31
+ prosoon-pf
32
+ ```
33
+
34
+ Pick a port and a directory:
35
+
36
+ ```bash
37
+ prosoon-pf 9000 --dir ~/share
38
+ ```
39
+
40
+ Expose it publicly (prints a `https://….trycloudflare.com` link):
41
+
42
+ ```bash
43
+ prosoon-pf 8000 --public
44
+ ```
45
+
46
+ If `cloudflared` isn't installed, `--public` mode will ask before installing it.
47
+
48
+ ## Public mode is public
49
+
50
+ When you use `--public`, **every file in the served directory becomes
51
+ downloadable by anyone with the link** for as long as the command runs. The tool
52
+ shows you the directory contents and asks for confirmation first. Don't serve a
53
+ folder that contains passwords, keys, `.ovpn` files, or anything private.
54
+
55
+ ## License
56
+
57
+ MIT
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/prosoon_pf/__init__.py
4
+ src/prosoon_pf/cli.py
5
+ src/prosoon_pf.egg-info/PKG-INFO
6
+ src/prosoon_pf.egg-info/SOURCES.txt
7
+ src/prosoon_pf.egg-info/dependency_links.txt
8
+ src/prosoon_pf.egg-info/entry_points.txt
9
+ src/prosoon_pf.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ prosoon-pf = prosoon_pf.cli:main
@@ -0,0 +1 @@
1
+ prosoon_pf