trqsh 0.1.1__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.
trqsh/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """trqsh — expose localhost to the internet.
2
+
3
+ This PyPI package is a thin wrapper that fetches the prebuilt ``trqsh`` CLI
4
+ binary for your platform (from the GitHub release) and execs it. The real
5
+ program is the Go binary; see https://trqsh.uz.
6
+ """
7
+
8
+ __version__ = "0.1.1"
trqsh/__main__.py ADDED
@@ -0,0 +1,22 @@
1
+ """Console entry point: exec the real trqsh binary with the caller's arguments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+
9
+ from ._runtime import ensure_binary
10
+
11
+
12
+ def main() -> None:
13
+ binary = str(ensure_binary())
14
+ args = [binary, *sys.argv[1:]]
15
+ if os.name == "nt":
16
+ # os.execv on Windows doesn't preserve the console cleanly; use subprocess.
17
+ sys.exit(subprocess.run(args).returncode)
18
+ os.execv(binary, args)
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
trqsh/_runtime.py ADDED
@@ -0,0 +1,132 @@
1
+ """Locate (or download) the prebuilt trqsh binary for the current platform.
2
+
3
+ The archive names mirror the goreleaser template
4
+ ``trqsh_<version>_<os>_<arch>.<ext>`` (.zip on Windows, .tar.gz elsewhere),
5
+ downloaded from the GitHub release and verified against ``checksums.txt``. Only
6
+ the Python standard library is used.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import os
13
+ import platform
14
+ import stat
15
+ import sys
16
+ import tarfile
17
+ import tempfile
18
+ import urllib.request
19
+ import zipfile
20
+ from pathlib import Path
21
+
22
+ from . import __version__
23
+
24
+ REPO = os.environ.get("TRQSH_REPO", "trqsh-uz/downloads")
25
+ VERSION = os.environ.get("TRQSH_VERSION", __version__)
26
+
27
+
28
+ def _target() -> tuple[str, str, str]:
29
+ goos = {"darwin": "darwin", "linux": "linux", "windows": "windows"}.get(
30
+ platform.system().lower()
31
+ )
32
+ goarch = {
33
+ "x86_64": "amd64",
34
+ "amd64": "amd64",
35
+ "arm64": "arm64",
36
+ "aarch64": "arm64",
37
+ }.get(platform.machine().lower())
38
+ if not goos or not goarch:
39
+ raise SystemExit(
40
+ f"trqsh: unsupported platform {platform.system()}/{platform.machine()}. "
41
+ f"Download manually from https://github.com/{REPO}/releases"
42
+ )
43
+ ext = "zip" if goos == "windows" else "tar.gz"
44
+ return goos, goarch, ext
45
+
46
+
47
+ def _bin_name() -> str:
48
+ return "trqsh.exe" if platform.system() == "Windows" else "trqsh"
49
+
50
+
51
+ def _cache_dir() -> Path:
52
+ base = os.environ.get("XDG_CACHE_HOME") or (Path.home() / ".cache")
53
+ d = Path(base) / "trqsh" / VERSION
54
+ d.mkdir(parents=True, exist_ok=True)
55
+ return d
56
+
57
+
58
+ def bin_path() -> Path:
59
+ return _cache_dir() / _bin_name()
60
+
61
+
62
+ def _download(url: str) -> bytes:
63
+ req = urllib.request.Request(url, headers={"User-Agent": "trqsh-pypi-installer"})
64
+ with urllib.request.urlopen(req) as resp: # noqa: S310 (trusted GitHub host)
65
+ return resp.read()
66
+
67
+
68
+ def _verify(data: bytes, archive: str, base: str) -> None:
69
+ if os.environ.get("TRQSH_SKIP_CHECKSUM") == "1":
70
+ return
71
+ try:
72
+ sums = _download(f"{base}/checksums.txt").decode()
73
+ except Exception as exc: # noqa: BLE001
74
+ print(f"trqsh: warning — could not fetch checksums.txt ({exc}); skipping verify", file=sys.stderr)
75
+ return
76
+ want = None
77
+ for line in sums.splitlines():
78
+ parts = line.split()
79
+ if len(parts) == 2 and parts[1].lstrip("*") == archive:
80
+ want = parts[0].lower()
81
+ break
82
+ if not want:
83
+ print(f"trqsh: warning — {archive} absent from checksums.txt; skipping verify", file=sys.stderr)
84
+ return
85
+ got = hashlib.sha256(data).hexdigest()
86
+ if got != want:
87
+ raise SystemExit(f"trqsh: checksum mismatch for {archive}\n expected {want}\n got {got}")
88
+
89
+
90
+ def _extract(archive_path: Path, ext: str, dest: Path) -> None:
91
+ if ext == "zip":
92
+ with zipfile.ZipFile(archive_path) as zf:
93
+ zf.extractall(dest)
94
+ else:
95
+ with tarfile.open(archive_path) as tf:
96
+ try:
97
+ tf.extractall(dest, filter="data") # py3.12+ safe extraction
98
+ except TypeError:
99
+ tf.extractall(dest)
100
+
101
+
102
+ def ensure_binary() -> Path:
103
+ """Return the path to the trqsh binary, downloading it once if needed."""
104
+ target = bin_path()
105
+ if target.exists():
106
+ return target
107
+
108
+ goos, goarch, ext = _target()
109
+ archive = f"trqsh_{VERSION}_{goos}_{goarch}.{ext}"
110
+ # Overridable (TRQSH_DOWNLOAD_BASE) for mirrors, air-gapped installs, or tests.
111
+ base = os.environ.get("TRQSH_DOWNLOAD_BASE") or f"https://github.com/{REPO}/releases/download/v{VERSION}"
112
+
113
+ print(f"trqsh: downloading {archive} (v{VERSION})...", file=sys.stderr)
114
+ data = _download(f"{base}/{archive}")
115
+ _verify(data, archive, base)
116
+
117
+ tmp = Path(tempfile.mkdtemp())
118
+ archive_path = tmp / archive
119
+ archive_path.write_bytes(data)
120
+ _extract(archive_path, ext, _cache_dir())
121
+
122
+ if not target.exists():
123
+ for found in _cache_dir().rglob(target.name):
124
+ found.replace(target)
125
+ break
126
+ if not target.exists():
127
+ raise SystemExit("trqsh: binary not found after extraction")
128
+
129
+ if os.name != "nt":
130
+ target.chmod(target.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
131
+ print("trqsh: installed ✓", file=sys.stderr)
132
+ return target
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.4
2
+ Name: trqsh
3
+ Version: 0.1.1
4
+ Summary: Expose localhost to the internet — fast QUIC tunnels with a desktop app. Installs the trqsh CLI binary for your platform.
5
+ Project-URL: Homepage, https://trqsh.uz
6
+ Project-URL: Documentation, https://trqsh.uz/docs
7
+ Project-URL: Source, https://github.com/trqsh/trqsh
8
+ Project-URL: Issues, https://github.com/trqsh/trqsh/issues
9
+ Author-email: trqsh <ops@trqsh.uz>
10
+ License-Expression: Apache-2.0
11
+ Keywords: http3,localhost,ngrok-alternative,quic,trqsh,tunnel,webhook
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Internet :: Proxy Servers
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+
24
+ # trqsh (PyPI)
25
+
26
+ Install the [trqsh](https://trqsh.uz) CLI with pip. This package fetches the
27
+ signed, prebuilt binary for your platform from the GitHub release on first run.
28
+
29
+ ```bash
30
+ pip install trqsh
31
+ # or, isolated:
32
+ pipx install trqsh
33
+
34
+ trqsh http 3000 # expose localhost:3000 over a public HTTPS URL
35
+ ```
36
+
37
+ ## How it works
38
+
39
+ The console script `trqsh` calls `trqsh.ensure_binary()`, which detects your OS
40
+ (`darwin`/`linux`/`windows`) and CPU (`amd64`/`arm64`), downloads
41
+ `trqsh_<version>_<os>_<arch>.<ext>` from
42
+ `https://github.com/trqsh/trqsh/releases`, verifies its SHA-256 against
43
+ `checksums.txt`, caches the binary under `~/.cache/trqsh/<version>/`, and execs
44
+ it. Pure standard library — no runtime dependencies.
45
+
46
+ ## Environment overrides
47
+
48
+ | Variable | Purpose |
49
+ |---|---|
50
+ | `TRQSH_VERSION` | Pin a specific release (defaults to the package version). |
51
+ | `TRQSH_REPO` | Alternate `owner/repo` for the release download. |
52
+ | `TRQSH_DOWNLOAD_BASE` | Full base URL of a mirror serving the release assets (for air-gapped/internal mirrors). |
53
+ | `TRQSH_SKIP_CHECKSUM=1` | Skip SHA-256 verification (not recommended). |
54
+
55
+ Licensed Apache-2.0.
@@ -0,0 +1,7 @@
1
+ trqsh/__init__.py,sha256=0ndFNaVarZZrW22AVBXDa3YG7Tx1Aj16U54ya_iuifk,273
2
+ trqsh/__main__.py,sha256=6NLkSrBSCZ29-5CwE1E3MDhPaoAYtSO9NxPkC838gMA,512
3
+ trqsh/_runtime.py,sha256=XOhMlJfW5BLgFgfq1U4WLZl4cgVN-GSCvwFmle4-wFo,4304
4
+ trqsh-0.1.1.dist-info/METADATA,sha256=TgvQ7mM3zwCI_-Qze__ZwTRcKCXGs0xPQRy6epv_I-o,2135
5
+ trqsh-0.1.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
6
+ trqsh-0.1.1.dist-info/entry_points.txt,sha256=8Qs_xqNviQgcpJt29tpQYRJqM18qyw3nqSOd0IyeW4I,46
7
+ trqsh-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ trqsh = trqsh.__main__:main