caddyserver 2.11.7__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,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sushant Gautam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ The bundled `caddy` binaries are the official Caddy builds, distributed under
26
+ Caddy's own license (Apache-2.0) by Caddy Server Pty Ltd — see
27
+ https://github.com/caddyserver/caddy
@@ -0,0 +1,102 @@
1
+ Metadata-Version: 2.1
2
+ Name: caddyserver
3
+ Version: 2.11.7
4
+ Summary: Caddy web server as platform-specific Python wheels - pip/pipx/uvx installable, offline, no first-run download
5
+ Author: Sushant Gautam
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Project-URL: Homepage, https://github.com/SushantGautam/caddyserve-wheel
9
+ Project-URL: Repository, https://github.com/SushantGautam/caddyserve-wheel
10
+ Project-URL: Issues, https://github.com/SushantGautam/caddyserve-wheel/issues
11
+ Keywords: caddy,webserver,binary,distribution,http3,https
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: MacOS :: MacOS X
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
20
+ Description-Content-Type: text/markdown
21
+
22
+ # caddyserver
23
+
24
+ The [Caddy](https://caddyserver.com/) web server as a Python wheel — install with
25
+ `pip`/`uvx`, no mise, no Docker, no Homebrew, no first-run download.
26
+ **Works offline after install.** `pip`/`uv` picks the right binary for your OS
27
+ and architecture automatically (Windows, macOS, Linux; x86-64 and ARM64).
28
+
29
+ > [!IMPORTANT]
30
+ > **Community-maintained, not the Caddy project.** Official project:
31
+ > https://caddyserver.com / https://github.com/caddyserver/caddy. The binaries
32
+ > here are the official Caddy builds, repackaged as wheels.
33
+ > **Handover:** happy to **hand over the `caddyserver` PyPI project to the
34
+ > official Caddy team** if they want to maintain it — open an issue.
35
+
36
+ ## Quick run (no install)
37
+
38
+ ```console
39
+ uvx caddyserver version
40
+ uvx caddyserver file-server --listen :8080
41
+ ```
42
+
43
+ `uvx caddyserver ...` runs Caddy directly — anything after `caddyserver` is
44
+ passed straight to the Caddy CLI.
45
+
46
+ ## Install
47
+
48
+ ```console
49
+ pip install caddyserver # or: uv tool install caddyserver / pipx install caddyserver
50
+ caddy version
51
+ ```
52
+
53
+ ## CLI usage
54
+
55
+ The wheel puts the real `caddy` binary on your PATH, so the full official CLI
56
+ works as-is:
57
+
58
+ ```console
59
+ caddy version # show version
60
+ caddy file-server --listen :8080 # serve the current directory
61
+ caddy run --config Caddyfile # start with your Caddyfile
62
+ caddy validate --config Caddyfile # check config syntax
63
+ caddy adapt --config Caddyfile # Caddyfile -> JSON
64
+ caddy list modules # list compiled-in modules
65
+ ```
66
+
67
+ Any `caddy` subcommand works — this is the unmodified official binary.
68
+ A `caddyserver` console script does the same thing (Python launcher), useful
69
+ when `bin/` is not on PATH: `uvx caddyserver version`.
70
+
71
+ ### Programmatic use
72
+
73
+ ```python
74
+ from caddyserver import get_caddy_executable
75
+ exe = get_caddy_executable() # Path to the bundled caddy binary
76
+ ```
77
+
78
+ The package **version mirrors the Caddy version**
79
+ (e.g. `caddyserver==2.11.7` bundles Caddy v2.11.7).
80
+
81
+ ## Maintenance
82
+
83
+ Committed to tracking the **latest Caddy release** — a bump is one line plus a
84
+ tag, and the pipeline builds, verifies, and publishes automatically.
85
+ **If I am slow to pick up a new Caddy release, [open an issue](https://github.com/SushantGautam/caddyserve-wheel/issues) —
86
+ I will push a new version promptly.** PRs bumping `build/version.txt` welcome.
87
+
88
+ ## Verification
89
+
90
+ Every GitHub Release lists the **wheel SHA-256** and the **upstream Caddy
91
+ asset digest** for each file; the build fails hard if any downloaded asset
92
+ doesn't match the [official checksums](https://github.com/caddyserver/caddy/releases).
93
+
94
+ ## Version bump (maintainers)
95
+
96
+ ```bash
97
+ echo "2.12.0" > build/version.txt
98
+ # also update pyproject.toml version + src/caddyserver/__init__.py __version__
99
+ git commit -am "bump to 2.12.0"
100
+ git tag v2.12.0 && git push --tags # CI builds, verifies, publishes
101
+ ```
102
+
@@ -0,0 +1,80 @@
1
+ # caddyserver
2
+
3
+ The [Caddy](https://caddyserver.com/) web server as a Python wheel — install with
4
+ `pip`/`uvx`, no mise, no Docker, no Homebrew, no first-run download.
5
+ **Works offline after install.** `pip`/`uv` picks the right binary for your OS
6
+ and architecture automatically (Windows, macOS, Linux; x86-64 and ARM64).
7
+
8
+ > [!IMPORTANT]
9
+ > **Community-maintained, not the Caddy project.** Official project:
10
+ > https://caddyserver.com / https://github.com/caddyserver/caddy. The binaries
11
+ > here are the official Caddy builds, repackaged as wheels.
12
+ > **Handover:** happy to **hand over the `caddyserver` PyPI project to the
13
+ > official Caddy team** if they want to maintain it — open an issue.
14
+
15
+ ## Quick run (no install)
16
+
17
+ ```console
18
+ uvx caddyserver version
19
+ uvx caddyserver file-server --listen :8080
20
+ ```
21
+
22
+ `uvx caddyserver ...` runs Caddy directly — anything after `caddyserver` is
23
+ passed straight to the Caddy CLI.
24
+
25
+ ## Install
26
+
27
+ ```console
28
+ pip install caddyserver # or: uv tool install caddyserver / pipx install caddyserver
29
+ caddy version
30
+ ```
31
+
32
+ ## CLI usage
33
+
34
+ The wheel puts the real `caddy` binary on your PATH, so the full official CLI
35
+ works as-is:
36
+
37
+ ```console
38
+ caddy version # show version
39
+ caddy file-server --listen :8080 # serve the current directory
40
+ caddy run --config Caddyfile # start with your Caddyfile
41
+ caddy validate --config Caddyfile # check config syntax
42
+ caddy adapt --config Caddyfile # Caddyfile -> JSON
43
+ caddy list modules # list compiled-in modules
44
+ ```
45
+
46
+ Any `caddy` subcommand works — this is the unmodified official binary.
47
+ A `caddyserver` console script does the same thing (Python launcher), useful
48
+ when `bin/` is not on PATH: `uvx caddyserver version`.
49
+
50
+ ### Programmatic use
51
+
52
+ ```python
53
+ from caddyserver import get_caddy_executable
54
+ exe = get_caddy_executable() # Path to the bundled caddy binary
55
+ ```
56
+
57
+ The package **version mirrors the Caddy version**
58
+ (e.g. `caddyserver==2.11.7` bundles Caddy v2.11.7).
59
+
60
+ ## Maintenance
61
+
62
+ Committed to tracking the **latest Caddy release** — a bump is one line plus a
63
+ tag, and the pipeline builds, verifies, and publishes automatically.
64
+ **If I am slow to pick up a new Caddy release, [open an issue](https://github.com/SushantGautam/caddyserve-wheel/issues) —
65
+ I will push a new version promptly.** PRs bumping `build/version.txt` welcome.
66
+
67
+ ## Verification
68
+
69
+ Every GitHub Release lists the **wheel SHA-256** and the **upstream Caddy
70
+ asset digest** for each file; the build fails hard if any downloaded asset
71
+ doesn't match the [official checksums](https://github.com/caddyserver/caddy/releases).
72
+
73
+ ## Version bump (maintainers)
74
+
75
+ ```bash
76
+ echo "2.12.0" > build/version.txt
77
+ # also update pyproject.toml version + src/caddyserver/__init__.py __version__
78
+ git commit -am "bump to 2.12.0"
79
+ git tag v2.12.0 && git push --tags # CI builds, verifies, publishes
80
+ ```
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ # NOTE: keep in sync with build/version.txt (build/wheels.py verifies this).
7
+ version = "2.11.7"
8
+ name = "caddyserver"
9
+ description = "Caddy web server as platform-specific Python wheels — pip/pipx/uvx installable, offline, no first-run download"
10
+ readme = "README.md"
11
+ requires-python = ">=3.9"
12
+ license = { text = "MIT" }
13
+ authors = [{ name = "Sushant Gautam" }]
14
+ keywords = ["caddy", "webserver", "binary", "distribution", "http3", "https"]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Environment :: Console",
18
+ "Intended Audience :: System Administrators",
19
+ "Operating System :: Microsoft :: Windows",
20
+ "Operating System :: MacOS :: MacOS X",
21
+ "Operating System :: POSIX :: Linux",
22
+ "Programming Language :: Python :: 3",
23
+ "Topic :: Internet :: WWW/HTTP :: Servers",
24
+ ]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/SushantGautam/caddyserve-wheel"
28
+ Repository = "https://github.com/SushantGautam/caddyserve-wheel"
29
+ Issues = "https://github.com/SushantGautam/caddyserve-wheel/issues"
30
+
31
+ [project.scripts]
32
+ # Only `caddyserver`: a `caddy` entry point would clobber the real caddy
33
+ # binary that ships in the wheel's .data/scripts/ and lands on PATH.
34
+ caddyserver = "caddyserver.cli:main"
35
+
36
+ [tool.hatch.build.targets.wheel]
37
+ packages = ["src/caddyserver"]
@@ -0,0 +1,9 @@
1
+ """caddyserver: official Caddy binaries distributed as Python wheels.
2
+
3
+ The package version mirrors the bundled Caddy version.
4
+ """
5
+
6
+ __version__ = "2.11.7"
7
+ CADDY_VERSION = __version__
8
+
9
+ from caddyserver.cli import get_caddy_executable, main # noqa: E402,F401
@@ -0,0 +1,63 @@
1
+ """Console entry point: locate the bundled Caddy binary and exec it.
2
+
3
+ The binary ships inside the wheel under ``.data/scripts/`` and is installed
4
+ by pip into the same directory as the interpreter's executable
5
+ (venv/pipx ``bin/`` on POSIX, the Scripts-less venv root on Windows).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ import sysconfig
13
+ from pathlib import Path
14
+
15
+ _BIN = "caddy.exe" if os.name == "nt" else "caddy"
16
+
17
+
18
+ class CaddyNotFound(Exception):
19
+ pass
20
+
21
+
22
+ def get_caddy_executable() -> Path:
23
+ """Return the path to the bundled Caddy binary."""
24
+ candidates = []
25
+
26
+ # Primary: pip installs .data/scripts/ into the scheme scripts dir,
27
+ # which is exactly where the `caddy`/`caddyserver` console scripts live.
28
+ # (Do NOT resolve sys.executable: venvs symlink the interpreter to the
29
+ # base install, which would point at the wrong bin/.)
30
+ candidates.append(Path(sysconfig.get_path("scripts")) / _BIN)
31
+ candidates.append(Path(sys.executable).parent / _BIN)
32
+
33
+ # Fallbacks: relative to this package (uninstalled tree, exotic layouts).
34
+ # `pkg` is the caddyserver package dir; `pkg.parent` is site-packages.
35
+ pkg = Path(__file__).resolve().parent
36
+ sp = pkg.parent
37
+ candidates += [
38
+ sp / _BIN,
39
+ pkg / _BIN,
40
+ # uv (uvx / `uv tool install`) leaves the .data tree under
41
+ # site-packages instead of moving it to the scheme scripts dir.
42
+ sp / ".data" / "scripts" / _BIN,
43
+ sp / "data" / "scripts" / _BIN,
44
+ pkg.parent.parent / "bin" / _BIN,
45
+ ]
46
+
47
+ for c in candidates:
48
+ c = c.resolve() if c.exists() else c
49
+ if c.is_file():
50
+ return c
51
+
52
+ raise CaddyNotFound(
53
+ "Bundled Caddy binary not found. Looked for:\n "
54
+ + "\n ".join(str(c) for c in candidates)
55
+ + "\nWas this package installed from a platform wheel, not the source tree?"
56
+ )
57
+
58
+
59
+ def main(argv: list[str] | None = None) -> int:
60
+ exe = get_caddy_executable()
61
+ args = sys.argv[1:] if argv is None else argv
62
+ os.execvp(str(exe), [str(exe), *args])
63
+ # Unreachable on success; execvp raises on failure.
@@ -0,0 +1 @@
1
+ 2.11.7
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ """Build platform-specific wheels bundling official Caddy binaries.
3
+
4
+ Pure standard library. Flow:
5
+ 1. Read the pinned Caddy version from build/version.txt.
6
+ 2. Download caddy_{v}_checksums.txt from the official caddyserver/caddy
7
+ GitHub release.
8
+ 3. Download each platform archive, verify its SHA-256 (hard fail on mismatch).
9
+ 4. Emit one wheel per platform tag plus an sdist into dist/, and write
10
+ build/release_manifest.json (upstream + wheel digests for release notes).
11
+
12
+ Usage: python build/wheels.py
13
+ Env: CADDY_CHECKSUMS_URL override for testing (not used in CI).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import base64
19
+ import hashlib
20
+ import io
21
+ import json
22
+ import os
23
+ import re
24
+ import shutil
25
+ import sys
26
+ import tarfile
27
+ import time
28
+ import urllib.request
29
+ import zipfile
30
+ from concurrent.futures import ThreadPoolExecutor
31
+ from pathlib import Path
32
+
33
+ REPO = Path(__file__).resolve().parent.parent
34
+ DIST = REPO / "dist"
35
+
36
+ # (upstream caddy asset suffix, wheel platform tag, binary name in archive)
37
+ PLATFORMS = [
38
+ ("windows_amd64.zip", "win_amd64", "caddy.exe"),
39
+ ("windows_arm64.zip", "win_arm64", "caddy.exe"),
40
+ ("mac_amd64.tar.gz", "macosx_11_0_x86_64", "caddy"),
41
+ ("mac_arm64.tar.gz", "macosx_11_0_arm64", "caddy"),
42
+ ("linux_amd64.tar.gz", "manylinux2014_x86_64.musllinux_1_1_x86_64", "caddy"),
43
+ ("linux_arm64.tar.gz", "manylinux2014_aarch64.musllinux_1_1_aarch64", "caddy"),
44
+ ]
45
+
46
+ PKG_NAME = "caddyserver"
47
+ DIST_NAME = "caddyserver"
48
+ REQUIRES_PYTHON = ">=3.9"
49
+
50
+
51
+ def shorthash(data: bytes) -> str:
52
+ return base64.urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=").decode()
53
+
54
+
55
+ def load_version() -> str:
56
+ v = (REPO / "build" / "version.txt").read_text().strip()
57
+ if not re.fullmatch(r"\d+\.\d+\.\d+", v):
58
+ sys.exit(f"bad version in build/version.txt: {v!r} (expect X.Y.Z)")
59
+ return v
60
+
61
+
62
+ def fetch(url: str) -> bytes:
63
+ req = urllib.request.Request(url, headers={"User-Agent": "caddyserver-builder"})
64
+ with urllib.request.urlopen(req, timeout=120) as r:
65
+ return r.read()
66
+
67
+
68
+ def parse_checksums(text: str) -> dict[str, str]:
69
+ # Official Caddy releases publish full SHA-512 (128 hex) digests in
70
+ # checksums.txt. Accept 64- or 128-char digests for forward compatibility.
71
+ out: dict[str, str] = {}
72
+ for line in text.splitlines():
73
+ m = re.match(r"^([0-9a-f]{64,128})\s+\*?(\S+)$", line.strip())
74
+ if m:
75
+ out[m.group(2)] = m.group(1)
76
+ return out
77
+
78
+
79
+ def digest_of(blob: bytes, pub_len: int) -> str:
80
+ algo = hashlib.sha512 if pub_len == 128 else hashlib.sha256
81
+ return algo(blob).hexdigest()
82
+
83
+
84
+ def build_wheel(
85
+ version: str, tag: str, binary: Path, metadata: dict, readme: str, out: Path
86
+ ) -> None:
87
+ exe = binary.read_bytes()
88
+ dist_info = f"{PKG_NAME}-{version}.dist-info"
89
+ files: dict[str, bytes] = {
90
+ f"{PKG_NAME}/__init__.py": (REPO / "src" / PKG_NAME / "__init__.py").read_bytes(),
91
+ f"{PKG_NAME}/cli.py": (REPO / "src" / PKG_NAME / "cli.py").read_bytes(),
92
+ f".data/scripts/{binary.name}": exe,
93
+ f"{dist_info}/METADATA": metadata,
94
+ # NOTE: only the `caddyserver` entry point is defined. A `caddy`
95
+ # entry point would collide with the real `caddy` binary pip installs
96
+ # from .data/scripts (the generated wrapper would clobber it).
97
+ f"{dist_info}/entry_points.txt": (
98
+ "[console_scripts]\n"
99
+ f"{DIST_NAME} = {PKG_NAME}.cli:main\n"
100
+ ).encode(),
101
+ f"{dist_info}/WHEEL": (
102
+ "Wheel-Version: 1.0\n"
103
+ "Generator: caddyserver build/wheels.py\n"
104
+ "Root-Is-Purelib: false\n"
105
+ "Tag: py3-none-" + tag + "\n"
106
+ ).encode(),
107
+ }
108
+
109
+ record_lines = [
110
+ f"{name},{shorthash(data)},{len(data)}" for name, data in sorted(files.items())
111
+ ]
112
+ record_lines.append(f"{dist_info}/RECORD,,")
113
+ files[f"{dist_info}/RECORD"] = ("\n".join(record_lines) + "\n").encode()
114
+
115
+ out.parent.mkdir(parents=True, exist_ok=True)
116
+ zf = zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED, compresslevel=6)
117
+ for name, data in sorted(files.items()):
118
+ zi = zipfile.ZipInfo(name)
119
+ zi.date_time = (2024, 1, 1, 0, 0, 0)
120
+ # External attrs must carry the full file mode in the high 16 bits,
121
+ # including the regular-file bits: pip checks stat.S_ISREG(mode) before
122
+ # restoring the executable bit on extracted data/scripts files.
123
+ if name == f".data/scripts/{binary.name}":
124
+ zi.external_attr = 0o100755 << 16
125
+ else:
126
+ zi.external_attr = 0o100644 << 16
127
+ zf.writestr(zi, data)
128
+ zf.close()
129
+ print(f" wrote {out.name} ({out.stat().st_size / 1e6:.1f} MB)")
130
+
131
+
132
+ def main() -> None:
133
+ version = load_version()
134
+ print(f"== caddyserver build: Caddy v{version}")
135
+
136
+ checksums = parse_checksums(fetch(
137
+ f"https://github.com/caddyserver/caddy/releases/download/v{version}/caddy_{version}_checksums.txt"
138
+ ).decode())
139
+ missing = [a for a, _, _ in PLATFORMS if f"caddy_{version}_{a}" not in checksums]
140
+ if missing:
141
+ sys.exit(f"assets missing from checksums.txt: {missing}")
142
+
143
+ work = REPO / "build" / "work"
144
+ shutil.rmtree(work, ignore_errors=True)
145
+ work.mkdir(parents=True)
146
+
147
+ def get_binary(args: tuple) -> Path:
148
+ suffix, sha, bin_name = args
149
+ sub = work / suffix.replace(".", "_")
150
+ sub.mkdir(parents=True)
151
+ blob = fetch(
152
+ f"https://github.com/caddyserver/caddy/releases/download/v{version}/caddy_{version}_{suffix}"
153
+ )
154
+ actual = digest_of(blob, len(sha))
155
+ if actual != sha:
156
+ sys.exit(f"CHECKSUM MISMATCH {suffix}: expected {sha}, got {actual}")
157
+ archive = sub / f"caddy_{version}_{suffix}"
158
+ archive.write_bytes(blob)
159
+ extract = sub / "extract"
160
+ extract.mkdir()
161
+ if suffix.endswith(".zip"):
162
+ with zipfile.ZipFile(archive) as zf:
163
+ zf.extractall(extract)
164
+ else:
165
+ with tarfile.open(archive, "r:gz") as tf:
166
+ tf.extractall(extract, filter="data")
167
+ for f in extract.iterdir():
168
+ if f.is_file() and f.name == bin_name:
169
+ return f
170
+ sys.exit(f"binary {bin_name} not found in {suffix}")
171
+
172
+ print("== downloading + verifying binaries (6 in parallel)")
173
+ jobs = [(suffix, checksums[f"caddy_{version}_{suffix}"], bin_name) for suffix, _, bin_name in PLATFORMS]
174
+ with ThreadPoolExecutor(max_workers=6) as pool:
175
+ binaries = list(pool.map(get_binary, jobs))
176
+
177
+ # Sanity: every binary must report the right version.
178
+ import subprocess
179
+
180
+ host_arch = os.uname().machine # arm64/x86_64 on macOS, etc.
181
+ for (suffix, tag, _), bin_path in zip(PLATFORMS, binaries):
182
+ if os.uname().sysname != "Darwin":
183
+ continue
184
+ host_tag = "macosx_11_0_arm64" if host_arch in ("arm64", "aarch64") else "macosx_11_0_x86_64"
185
+ if tag != host_tag:
186
+ continue
187
+ bin_path.chmod(0o755)
188
+ r = subprocess.run([str(bin_path), "version"], capture_output=True, text=True)
189
+ first = r.stdout.splitlines()[0] if r.stdout else r.stderr
190
+ print(f" {tag}: {first}")
191
+
192
+ DIST.mkdir(exist_ok=True)
193
+ readme = (REPO / "README.md").read_text() if (REPO / "README.md").exists() else ""
194
+
195
+ metadata = (
196
+ "Metadata-Version: 2.1\n"
197
+ f"Name: {DIST_NAME}\n"
198
+ f"Version: {version}\n"
199
+ "Summary: Caddy web server as platform-specific Python wheels - pip/pipx/uvx "
200
+ "installable, offline, no first-run download\n"
201
+ "Author: Sushant Gautam\n"
202
+ "License: MIT\n"
203
+ f"Requires-Python: {REQUIRES_PYTHON}\n"
204
+ "Project-URL: Homepage, https://github.com/SushantGautam/caddyserve-wheel\n"
205
+ "Project-URL: Repository, https://github.com/SushantGautam/caddyserve-wheel\n"
206
+ "Project-URL: Issues, https://github.com/SushantGautam/caddyserve-wheel/issues\n"
207
+ "Keywords: caddy,webserver,binary,distribution,http3,https\n"
208
+ "Classifier: Development Status :: 5 - Production/Stable\n"
209
+ "Classifier: Environment :: Console\n"
210
+ "Classifier: Intended Audience :: System Administrators\n"
211
+ "Classifier: Operating System :: Microsoft :: Windows\n"
212
+ "Classifier: Operating System :: MacOS :: MacOS X\n"
213
+ "Classifier: Operating System :: POSIX :: Linux\n"
214
+ "Classifier: Programming Language :: Python :: 3\n"
215
+ "Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server\n"
216
+ "Description-Content-Type: text/markdown\n"
217
+ # PEP 643: the description body is everything after the single blank
218
+ # line that ends the header block (there is no "Description:" field).
219
+ f"\n{readme}\n"
220
+ ).encode()
221
+
222
+ print("== building wheels")
223
+ manifest: list[dict] = []
224
+ for (suffix, tag, _), bin_path in zip(PLATFORMS, binaries):
225
+ out = DIST / f"{PKG_NAME}-{version}-py3-none-{tag}.whl"
226
+ build_wheel(version, tag, bin_path, metadata, readme, out)
227
+ manifest.append({
228
+ "file": out.name,
229
+ "sha256": hashlib.sha256(out.read_bytes()).hexdigest(),
230
+ "size": out.stat().st_size,
231
+ "upstream_asset": f"caddy_{version}_{suffix}",
232
+ "upstream_digest": checksums[f"caddy_{version}_{suffix}"],
233
+ })
234
+
235
+ # sdist (source only; wheels are produced by build/wheels.py or CI)
236
+ print("== building sdist")
237
+ sdist = DIST / f"{DIST_NAME}-{version}.tar.gz"
238
+ with tarfile.open(sdist, "w:gz") as tf:
239
+ for f in [
240
+ REPO / "pyproject.toml",
241
+ REPO / "README.md",
242
+ REPO / "LICENSE",
243
+ REPO / "build" / "version.txt",
244
+ REPO / "build" / "wheels.py",
245
+ REPO / "src",
246
+ ]:
247
+ if f.exists():
248
+ tf.add(f, arcname=f"{DIST_NAME}-{version}/{f.name}", recursive=True)
249
+ # PEP 643 / PyPI requirement: sdist needs a PKG-INFO with Metadata-Version
250
+ info = tarfile.TarInfo(f"{DIST_NAME}-{version}/PKG-INFO")
251
+ info.size = len(metadata)
252
+ info.mtime = int(time.time())
253
+ tf.addfile(info, io.BytesIO(metadata))
254
+ manifest.append({
255
+ "file": sdist.name,
256
+ "sha256": hashlib.sha256(sdist.read_bytes()).hexdigest(),
257
+ "size": sdist.stat().st_size,
258
+ "upstream_asset": None,
259
+ "upstream_digest": None,
260
+ })
261
+
262
+ (REPO / "build" / "release_manifest.json").write_text(json.dumps(manifest, indent=2))
263
+ print("== done")
264
+ for m in manifest:
265
+ print(f" {m['file']} sha256={m['sha256'][:16]}… {m['size'] / 1e6:.1f} MB")
266
+
267
+
268
+ if __name__ == "__main__":
269
+ main()