One feed, several packages: OpenWrt distribution notes for Freedom Tech
- One feed, several packages: OpenWrt distribution notes for Freedom Tech
- 1 — A feed is two different things, and people conflate them
- 2 — One feed can host several projects; four feeds cannot share their dependencies
- 3 — Upstream is a destination, not a strategy
- 4 — Version normalisation will bite you, and the ordering trap is worse than the syntax trap
- 5 — One build, two channels, identical bytes
- Recommendation
One feed, several packages: OpenWrt distribution notes for Freedom Tech
We ship TollGate — an OpenWrt router turned Cashu payment gateway — as .ipk/.apk for eight
architectures. Upstream CI used to do it. Our CI provider then disabled Actions for the whole
organisation (a support ticket is open), which forced a question worth answering properly either
way: what is the right shape for OpenWrt distribution when you cannot rely on a hosted runner, and
you are not one project but several (TollGate, FIPS, TollGate-RS, MPTCP bonding)?
Here is what we learned, and what we recommend. If you maintain anything that ships to routers,
most of this applies to you.
1 — A feed is two different things, and people conflate them
A build-time feed is feeds.conf: src-git <name> <url>[;branch|^commit], consumed by
./scripts/feeds update / install, used when you build your own image. A runtime feed is
/etc/opkg.conf or /etc/apk/repositories, consumed by the package manager on a running router.
You need both, and they fail differently. If you only publish files (as we do over Blossom and
Nostr kind-1063 events), you have no runtime feed: no opkg update, no clean upgrade path, no
removal, no index trust. Artifacts are a manual install, not a package manager. That is fine for
previews and for routers on old releases — and it is not fine as a sole strategy.
2 — One feed can host several projects; four feeds cannot share their dependencies
OpenWrt’s own guidance is blunt: dependencies between packages living in different non-base feeds
are strongly discouraged, because you cannot guarantee every build system has every feed. The day
TollGate depends on a FIPS package, per-project feeds become a build error.
The cost also sits in the tooling, and it is per-feed, not per-project: index generation, the SDK
matrix, signing, publish automation, and one src-git line for users who want the whole
mesh-plus-payments stack. Official OpenWrt feeds are themselves multi-project (packages, luci,
routing, telephony). Splitting buys compartmentalisation you can get more cheaply from CI
gating: publish the index only when the full matrix is green.
The counter-argument is trust. FIPS is not ours — shipping it from a payments org’s feed implies
ownership nobody has. That is exactly why the feed lives in a neutral org, with per-package
PKG_MAINTAINER/CODEOWNERS pointing back at each upstream repo, and the version/hash bump kept to a
one-line PR that upstream bots can open.
3 — Upstream is a destination, not a strategy
Getting a package into openwrt/packages is the right long-term home. It is also a trap if you do
it too early:
- New packages land on
masteronly. Stable branches take fixes. A merge today reaches the next
stable release — nobody who is currently running a router. - The bar is real and mechanical:
PKG_MAINTAINERasName <email>, an SPDXPKG_LICENSEplus
PKG_LICENSE_FILES, immutable source archives withPKG_HASH(moving “latest” and branch HEAD
are explicitly forbidden),procdinit withUSE_PROCD=1,conffilesregistered, and CI that
builds ~10 architectures and runs the binaries with--version/--help, requiring your
version string to appear in the output. - Alphas and per-commit dev builds contradict feed expectations. Ship those from your own feed.
So: land the package upstream when a stable, non-alpha release exists and the CLI/ABI has settled —
one package at a time, starting with the one you own outright. Until then, keep the package
directory byte-identical to what upstream would accept, so the eventual PR is one directory and no
debate.
4 — Version normalisation will bite you, and the ordering trap is worse than the syntax trap
Take a tag v0.6.0-alpha2 and naively write PKG_VERSION:=0.6.0-alpha2. The system composes
VERSION=0.6.0-alpha2-r1.
- opkg copes (it splits at the final
-), but itsverrevcmpranks0.6.0_alpha2above
0.6.0. Your alpha never upgrades to the release. - apk (OpenWrt 25.12+) does not even accept that string: its grammar allows
-only as
-r<digits>. Invalid version, package will not install.
The fix is a per-format suffix: _alphaN for apk targets, ~alphaN for opkg targets, selected from
the SDK configuration. Version dev builds with PKG_SOURCE_DATE+PKG_SOURCE_VERSION, never put your
own -rN inside PKG_VERSION, and never publish a moving “latest” — that rule applies to your feed
exactly as it does upstream.
Also verify the toolchain before you promise an old release: our go.mod requires Go 1.25, the
23.05 feed ships Go 1.21 and 24.10 ships 1.23. Those SDKs cannot build it. Declare your supported
matrix honestly and fail closed with a clear message instead of producing a broken package.
5 — One build, two channels, identical bytes
The Nostr/Blossom channel and the OpenWrt feed should not drift. If the same artifact is offered
both ways it must be the same file with the same sha256, built once, versioned once — package version
0.6.0~alpha2-r1 derived from tag v0.6.0-alpha2, and the same version and architecture recorded
inside the 1063 event. Convergence belongs at the artifact, not at the discovery layer. And publish
indices atomically: a half-written index (7 of 8 architectures) reads as a broken feed, or worse, as
a downgrade.
Recommendation
One multi-project feed, run as release engineering, in a neutral org. Upstream PRs sequenced after
the feed is stable, one package at a time, starting with the package you own. Hosted-runner
independence as a design requirement, not a contingency — the CI you depend on can disappear without
your consent, and today ours did.
First three steps: fix the version scheme and prove the ordering on both package managers; get the
feed building from immutable upstream release tarballs with signed, atomically published indices; then
keep the package directory PR-ready for the day a stable release makes upstreaming worth it.
Felix
Write a comment