1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
# mkwheels — Design
Date: 2026-06-26
Author: danix <danix@danix.xyz>
License: GPLv2 (v2-only)
## Goal
A standalone bash CLI that generates a **reproducible, pinned Python wheels
tarball** for a given package + version. Primary use: vendoring a Python
project's full dependency tree into a SlackBuild so the build installs into a
venv from local wheels with no network (the feroxbuster vendored-crates
pattern, applied to Python). Generic over package/version, so it serves any
future Python SBo, not just netexec.
## Repo
`~/Programming/GIT/mkwheels`, its own git repo. GPLv2 (v2-only): full `LICENSE`
text, a per-file header notice `Copyright (C) 2026 Danilo M. <danix@danix.xyz>`,
and a License section in the README.
## Files
```
mkwheels/
├── mkwheels # the script (single-file bash)
├── LICENSE # GPLv2 full text (from gnu.org)
└── README.md # usage, reproducibility rationale, SBo integration
```
## Dependencies
Runtime tools the script requires (documented in README, checked at startup):
- `bash`
- `python3` + `pip` (the venv + download engine)
- `jq` (parse the PyPI JSON API)
- `tar`, `gzip`, `md5sum`, `curl` (or `wget`)
No third-party Python packages; the tool only drives `pip`.
## Interface
```
mkwheels <pkg> <ver> [epoch]
```
- `-h` / `--help` → usage.
- `<pkg>` `<ver>` required.
- `[epoch]` optional SOURCE_DATE_EPOCH. If omitted, auto-derived from the
PyPI release upload time (see below) and a warning is printed. Pass it
explicitly to override.
- `OUTPUT` env var overrides the output directory (defaults to `$PWD`),
matching the mksboarchive convention.
## Flow
1. Parse args; handle `-h`. Require `<pkg>` and `<ver>`. Check required tools
are on PATH; fail clearly if not.
2. **Resolve epoch.** If `[epoch]` was passed, use it. Otherwise fetch
`https://pypi.org/pypi/<pkg>/<ver>/json`, take the earliest file's
`upload_time_iso_8601`, convert to epoch seconds with `jq` + `date`, and
warn that it was auto-derived. This is a real, reproducible,
package-tied timestamp.
3. Create a throwaway temp workdir; `trap` cleanup on exit.
4. `python3 -m venv` a throwaway build env (isolates pip from the host).
5. `pip download <pkg>==<ver> -d wheels/` — resolves the full tree including
git-sourced deps, building sdists to wheels.
6. Emit `requirements.txt`: every resolved distribution pinned to its exact
version with `--hash=sha256:...` entries computed from the downloaded
files. Pinned + hashed lockfile, shippable into the SlackBuild package dir.
7. **Reproducible tar.** Archive `wheels/` with normalized metadata so the
output is byte-identical for the same inputs + epoch:
- sorted entry order,
- `--mtime=@$EPOCH --owner=0 --group=0 --numeric-owner`,
- gzip `-n` (no embedded timestamp).
Output: `$OUTPUT/<pkg>-wheels-<ver>.tar.gz`.
8. Print the md5sum, the resolved epoch, and the output paths
(tarball + requirements.txt).
## Reproducibility
- PyPI releases are immutable, so the wheel set for a fixed version is
deterministic.
- **Git-sourced deps** (e.g. NetExec's impacket / certipy-ad / oscrypto /
pynfsclient) are unversioned upstream; `pip download` freezes whatever is
current at download time. Once the tarball is built it is the source of
truth; re-running months later may resolve newer git deps. The emitted
`requirements.txt` records the exact resolved versions for audit. This is
the same caveat the feroxbuster crates tarball carries.
- Tar normalization (step 7) makes the archive byte-identical for identical
inputs + epoch.
## Error handling
- `set -eu`.
- `trap` removes the temp workdir on any exit.
- Fail with a clear message if: a required tool is missing; the package/version
is not found on PyPI (HTTP error or empty JSON); `pip download` fails.
## Test (one runnable check)
A `selftest` path (or a small `test` script invoked manually) that runs
mkwheels twice on `six` (tiny, pure-Python, stable) and asserts the two
tarballs are byte-identical (md5 match). This is the smallest check that fails
if the reproducibility normalization breaks. No test framework.
## Out of scope (YAGNI)
- Resolving from an arbitrary input requirements.txt (the tool resolves from
`<pkg> <ver>`).
- Uploading the tarball anywhere (the maintainer uploads to
packages.danix.xyz by hand, as with the crates tarball).
- Non-PyPI indexes / private indexes.
- Caching or incremental rebuilds.
|