You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 1 of Linux distribution is shipped: release.yml builds .deb and .rpm packages on every release via nfpm and uploads them to GitHub Releases alongside the existing zip artifacts. Users can install with:
Phase 2 stands up native apt/yum repositories so users can run `sudo apt install wheels` once and `apt update && apt upgrade wheels` thereafter — no manual re-download per release. End-user UX:
Mint a GPG signing key for the Wheels project. Private half goes in 1Password under the existing `Infrastructure` vault. Public half ships as `tools/distribution-drafts/linux-packages/wheels-public.gpg` and is published at `apt.wheels.dev/wheels.gpg`.
Create `wheels-dev/apt-wheels-dev` repo — Cloudflare Pages site at `apt.wheels.dev`. Holds the static apt metadata tree (`Packages.gz`, `Release`, `Release.gpg`, `pool/*.deb`).
Create `wheels-dev/yum-wheels-dev` repo — Cloudflare Pages site at `yum.wheels.dev`. Holds the static yum metadata (`repodata/repomd.xml`, `pool/*.rpm`).
Author the metadata-generator workflow in each bucket repo. Listens for `repository_dispatch` from `wheels-dev/wheels`'s release workflow, downloads the new `.deb`/`.rpm` from the GitHub Release, regenerates the metadata (`apt-ftparchive` / `createrepo_c`), signs with GPG, commits, CF Pages auto-deploys.
`LINUX_REPO_GPG_PASSPHRASE` — passphrase for the private key
`LINUX_REPO_DISPATCH_TOKEN` — fine-grained PAT with write access to the two bucket repos
Wire the dispatch step in `release.yml` — extend the existing downstream-package-managers dispatch block (currently fires at `homebrew-wheels` and `scoop-wheels`) to also fire at `apt-wheels-dev` and `yum-wheels-dev`.
Update docs — once `apt.wheels.dev` resolves, replace the GitHub-Release download snippet in start-here/installing.mdx and command-line-tools/installation.mdx with the sources.list/yum.repo setup, and remove the "native apt/yum repos coming" ``.
Filename gotcha to handle in the metadata generator
GitHub Releases silently rewrites `` to `.` in uploaded asset filenames, so a `.deb` written to disk as `wheels_4.0.1snapshot.1700_amd64.deb` becomes `wheels_4.0.1.snapshot.1700_amd64.deb` at the URL. The metadata generator must compute the `.`-form to actually fetch the asset, then either rename to the canonical ``-form in `pool/` or accept the `.`-form and stay consistent. Either way works — the version field inside the package preserves `` so `dpkg`/`rpm` orders correctly. Documented in build-linux-packages.sh line 167-180.
Why two subdomains instead of one
Per the draft README: `packages.wheels.dev/apt/...` would work but produces uglier `sources.list` lines (`deb https://packages.wheels.dev/apt stable main`) vs the cleaner `deb https://apt.wheels.dev stable main`. Most projects (Docker, Caddy, Tailscale) use the two-subdomain pattern so Linux admins recognize the form.
Why this is a follow-up, not a v4.0 blocker
Phase 1 (download-then-install from GitHub Release) is fully working on Linux and ships with v4.0 GA via #2604. Phase 2 is a UX upgrade — users still get to wheels, just need to re-curl on each version bump until Phase 2 is live. The blocking work is mostly operational (mint key, set up CF Pages, store secrets) rather than code, which is why this fits better as a 4.0.x follow-up than a GA blocker.
Background
Phase 1 of Linux distribution is shipped:
release.ymlbuilds.deband.rpmpackages on every release via nfpm and uploads them to GitHub Releases alongside the existing zip artifacts. Users can install with:```bash
WHEELS_VERSION=$(curl -fsSL https://api.github.com/repos/wheels-dev/wheels/releases/latest \
| sed -nE 's/.*"tag_name": "v([^\"]+)"./\1/p')
curl -fsSLO "https://github.com/wheels-dev/wheels/releases/download/v\${WHEELS_VERSION}/wheels_\${WHEELS_VERSION}_amd64.deb\"
sudo apt install "./wheels_${WHEELS_VERSION}_amd64.deb"
```
Phase 1 docs land in #2604.
Phase 2 stands up native
apt/yumrepositories so users can run `sudo apt install wheels` once and `apt update && apt upgrade wheels` thereafter — no manual re-download per release. End-user UX:```bash
Debian / Ubuntu
curl -fsSL https://apt.wheels.dev/wheels.gpg \
| sudo tee /usr/share/keyrings/wheels.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/wheels.gpg] https://apt.wheels.dev stable main" \
| sudo tee /etc/apt/sources.list.d/wheels.list
sudo apt update && sudo apt install wheels
Fedora / RHEL
sudo dnf config-manager --add-repo https://yum.wheels.dev/wheels.repo
sudo dnf install wheels
```
Implementation plan
Fully sketched in tools/distribution-drafts/linux-packages/README.md § Phase 2. High-level:
start-here/installing.mdxandcommand-line-tools/installation.mdxwith the sources.list/yum.repo setup, and remove the "native apt/yum repos coming" ``.Filename gotcha to handle in the metadata generator
GitHub Releases silently rewrites `
` to `.` in uploaded asset filenames, so a `.deb` written to disk as `wheels_4.0.1snapshot.1700_amd64.deb` becomes `wheels_4.0.1.snapshot.1700_amd64.deb` at the URL. The metadata generator must compute the `.`-form to actually fetch the asset, then either rename to the canonical ``-form in `pool/` or accept the `.`-form and stay consistent. Either way works — the version field inside the package preserves `` so `dpkg`/`rpm` orders correctly. Documented in build-linux-packages.sh line 167-180.Why two subdomains instead of one
Per the draft README: `packages.wheels.dev/apt/...` would work but produces uglier `sources.list` lines (`deb https://packages.wheels.dev/apt stable main`) vs the cleaner `deb https://apt.wheels.dev stable main`. Most projects (Docker, Caddy, Tailscale) use the two-subdomain pattern so Linux admins recognize the form.
Why this is a follow-up, not a v4.0 blocker
Phase 1 (download-then-install from GitHub Release) is fully working on Linux and ships with v4.0 GA via #2604. Phase 2 is a UX upgrade — users still get to wheels, just need to re-curl on each version bump until Phase 2 is live. The blocking work is mostly operational (mint key, set up CF Pages, store secrets) rather than code, which is why this fits better as a 4.0.x follow-up than a GA blocker.
Related