Source repository backing https://apt.wheels.dev, the native Debian/Ubuntu package repository for the Wheels CFML framework CLI.
This repo holds the workflow + scripts + landing page + signing key. The apt metadata
tree (dists/) and the .deb pool (pool/) live in Cloudflare R2 (bucket wheels-apt),
served at https://apt.wheels.dev via R2 custom-domain. The receiver workflow regenerates
the metadata + uploads to R2 on every release. End users do:
# Stable channel
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
# Bleeding-edge — distinct package name (`wheels-be`) so it coexists with stable
echo "deb [signed-by=/usr/share/keyrings/wheels.gpg] https://apt.wheels.dev bleeding-edge main" \
| sudo tee /etc/apt/sources.list.d/wheels-be.list
sudo apt update && sudo apt install wheels-beReleases land in wheels-dev/wheels (or wheels-dev/wheels-snapshots for
bleeding-edge). The release workflow fires repository_dispatch here with
event type wheels-released and a {version, channel} payload. The receiver
at .github/workflows/wheels-released.yml
downloads the new .deb from the upstream GitHub Release, slots it into
pool/<channel>/w/<pkg>/, regenerates Packages.gz / Release / InRelease
via apt-ftparchive, signs with GPG, then uploads the new .deb + regenerated dists/ tree to R2.
R2's custom-domain edge serves the new files within seconds.
The receiver also supports workflow_dispatch for backfill / disaster-recovery
(re-add a missing version without waiting for a new release):
gh workflow run wheels-released.yml \
--repo wheels-dev/apt-wheels \
-f version=4.0.0 -f channel=stable| Path | Purpose |
|---|---|
.github/workflows/wheels-released.yml |
Receiver workflow — fires on repository_dispatch from wheels-dev/wheels. |
scripts/regenerate-apt-metadata.sh |
Pure-bash wrapper around apt-ftparchive + gpg. Idempotent — re-reads the entire pool/, rewrites dists/ from scratch. Safe to invoke by hand to repair a torn release. |
templates/aptftparchive.conf |
apt-ftparchive config (Origin, Label, Components, Architectures, compression). The wrapper script overrides Codename + Suite per-distribution at invocation time. |
index.html |
Plain-HTML landing page served at the apex. Shows the sources.list snippet so browsers hitting apt.wheels.dev see install instructions. |
wheels.gpg.placeholder |
Reminder file — must be replaced with the real ASCII-armored public key committed as wheels.gpg at the repo root before the first release publish. See "Operational setup" below. |
apt.wheels.dev/
├── wheels.gpg # ASCII-armored public key
├── dists/
│ ├── stable/
│ │ ├── Release
│ │ ├── Release.gpg
│ │ ├── InRelease # inline-signed Release (preferred)
│ │ └── main/binary-amd64/
│ │ ├── Packages
│ │ └── Packages.gz
│ └── bleeding-edge/
│ └── ... (mirror of the stable tree)
└── pool/
├── stable/
│ └── w/wheels/wheels_<v>_amd64.deb
└── bleeding-edge/
└── w/wheels-be/wheels-be_<v>_amd64.deb
| Channel | Package name | Pool path | apt install |
|---|---|---|---|
| Stable | wheels |
pool/stable/w/wheels/ |
apt install wheels |
| Bleeding-edge | wheels-be |
pool/bleeding-edge/w/wheels-be/ |
apt install wheels-be |
Distinct package names mean stable and bleeding-edge can be installed side-by-side on the same host — useful for users who want a working stable CLI alongside a BE one for testing.
GitHub Releases silently rewrites ~ to . in uploaded asset filenames, so:
- On disk locally (nfpm output):
wheels-be_4.0.1~snapshot.1787_amd64.deb - GitHub Release URL:
wheels-be_4.0.1.snapshot.1787_amd64.deb - Pool path (this repo):
pool/bleeding-edge/w/wheels-be/wheels-be_4.0.1~snapshot.1787_amd64.deb
The receiver workflow downloads using the .-form (the only form the GitHub
Release URL exposes), then renames to the canonical ~-form before publishing
into the pool. The version field inside the .deb metadata always carries
~, so dpkg --compare-versions orders snapshot releases below the next GA
release correctly regardless of which form sits in pool/.
Before this repo will publish a usable repository:
-
GPG signing key — generate a 4096-bit RSA key (or Ed25519) for
Wheels Distribution <hello@wheels.dev>. Private key + passphrase go into 1Password underop://Wheels/wheels-linux-repo-signing/(Wheels project vault onmy.1password.com— not the PAIop://Infrastructure/vault). Public key (ASCII-armored) replaceswheels.gpg.placeholderwith a committedwheels.gpgat the repo root. The same key is used by the parallelyum-wheelsbucket. -
Cloudflare R2 bucket — create the
wheels-aptbucket, attach theapt.wheels.devcustom domain. R2 has no per-object size limit (vs. Cloudflare Pages' 25 MiB) which is why we serve from R2 rather than Pages. -
CI secrets at https://github.com/wheels-dev/apt-wheels/settings/secrets/actions:
WHEELS_REPO_GPG_PRIVATE_KEY— ASCII-armored private keyWHEELS_REPO_GPG_PASSPHRASE— passphraseCLOUDFLARE_API_TOKEN— token withWorkers R2 Storage:Editon this account
-
Upstream dispatch token — on
wheels-dev/wheels, addLINUX_REPO_DISPATCH_TOKEN(fine-grained PAT withactions: writeon this repo and onwheels-dev/yum-wheels). The release workflow's dispatch step skips silently when this secret is unset, so the wiring is safe to land before the secret exists. -
Smoke-test — once the GPG key is in place and the secrets are set, run the receiver manually to backfill the current GA release:
gh workflow run wheels-released.yml \ --repo wheels-dev/apt-wheels \ -f version=4.0.1 -f channel=stable
Then verify on a fresh Debian/Ubuntu host:
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-cache policy wheels # should show the apt.wheels.dev origin sudo apt install wheels wheels --version
apt updatereportsGet:N https://apt.wheels.dev stable InReleaseon success. ANO_PUBKEYerror means the public key at/usr/share/keyrings/wheels.gpgdoesn't match the key the bucket signed with — verify withgpg --no-default-keyring --keyring /usr/share/keyrings/wheels.gpg --list-keys.
This repo was seeded from the templates at
wheels-dev/wheels → tools/distribution-drafts/apt-repo/.
Material changes to the workflow / scripts / config should generally land in
the upstream template too, so the next-time-we-rebuild-this story stays clean.
See issue #2605 for the
Phase 2 rollout plan.
MIT — see LICENSE.