Additive next to the opkg/24.10 lane — nothing existing changed. The same 4 packages (shaterd, shater-core, luci-app-shater, byedpi) are built through the official ImmortalWrt 25.12 apk-SDK and published as per-arch rolling releases apk-latest-<arch> / apk-<tag>-<arch> (x86_64, aarch64_cortex-a53). - ci/sdk-build-apk.sh: drives the 25.12 SDK inside debian:bookworm, compiles .apk, then `apk mkndx --root T --keys-dir T/keys --allow-untrusted --sign KEY --output packages.adb *.apk` — the exact form the OpenWrt 25.12 buildsystem uses (unsigned members, signed index). - ci/build-feed-apk.sh: per-arch runner entrypoint (same --volumes-from and artifact-order contract as ci/build-feed.sh). - ci/gen-apk-key.sh: one-shot EC (prime256v1) keypair generator; private half -> Gitea secret KEY_APK, public dist/shater-apk.pem committed. - release.yml: additive build-apk / release-apk jobs; `on:` triggers untouched (v* tags + workflow_dispatch); apk release tags deliberately non-`v*`. - docs-shater/INSTALL.md section 6, .gitignore (out-apk/), dist/shater-apk.pem. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 KiB
Shater v0.2 — Build & Install
How to build the ship artifact (the SPA-embedded shaterd binary) and install
the OpenWrt feed onto a router.
1. Build the shaterd binary
scripts/build-shaterd.sh produces the release binary for every router arch:
scripts/build-shaterd.sh [VERSION] [--fast]
What it does:
- Builds the admin SPA —
cd panel && npm ci && npm run build(Vite →panel/dist). - Copies
panel/dist/*intoshater/panel/webroot/, so//go:embed all:webrootbakes the real SPA into the binary (not the "SPA not embedded" placeholder). - Cross-builds, for each of
{amd64, arm64}, with the D9 musl-static router tag set (CGO_ENABLED=0 GOOS=linux), stripped + trimmed, intodist/shaterd-<arch>(uncompressed, kept for debugging). - UPX
--lzma --best(D10) →dist/shaterd-<arch>.upx(~42 MB → ~8–11 MB). - Stages
dist/shaterd-<arch>.upxintoopenwrt/shaterd/files/for the package. - Prints a size table + a per-arch static check (must be
ET_EXEC, noPT_INTERP).
Arg / env:
VERSION— stamped intoconstant.Version. Resolution: positional arg →$SHATER_VERSION→git describe --tags→v0.2.0-dev.--fast— skipnpm ciwhenpanel/node_modulesalready exists.UPX=/path/to/upx— override the UPX binary (defaultupxonPATH). UPX is cross-arch, so one host packs both the amd64 and aarch64 ELFs. (Note: UPX also reads$UPXas its own options — the scriptunsets it after capturing the path.)
Example (Windows host, Git Bash):
UPX="…/scratchpad/upx-4.2.4-win64/upx.exe" scripts/build-shaterd.sh v0.2.0 --fast
The dist/* and openwrt/shaterd/files/shaterd-*.upx outputs are gitignored —
they are release artifacts, not source.
Tag set (D9 — keep in sync with docs-shater/DECISIONS.md):
with_quic,with_wireguard,with_utls,
badlinkname,tfogo_checklinkname0,with_xhttp,with_awg,with_lx_command
We drop with_purego,with_naive_outbound: they pull cronet-go, which forces a
glibc PT_INTERP even under CGO_ENABLED=0, making the binary unusable on musl.
We drop with_gvisor: the shater data plane is tproxy/redirect and generate
never emits a tun inbound, so the userspace gvisor netstack is unreachable code.
We drop with_clash_api: the admin panel is shater's own web server and the
generator never emits a clash_api service, so the Clash server is dead code.
We drop with_dhcp: shater resolver types are udp/tcp/doh/dot/local/fakeip;
a dhcp:// DNS transport is never generated or registered.
2. Packages
Four OpenWrt packages live under openwrt/:
| Package | Arch | What it ships |
|---|---|---|
shaterd |
per-arch | Prebuilt static shaterd binary → /usr/bin/shaterd (this is the ship artifact from step 1). |
shater-core |
all | procd init (supervises shaterd run), cron, hotplug, sysctl, inert default UCI. DEPENDS:=+shaterd +kmod-nft-tproxy +kmod-nft-socket +ip-full. |
luci-app-shater |
all | Thin LuCI launcher: mini dashboard + token-handoff "Open panel" button. DEPENDS:=+shater-core +rpcd. |
byedpi |
per-arch | Optional ByeDPI (ciadpi) local desync SOCKS proxy for a type='byedpi' egress. |
Why shaterd is a prebuilt-binary package
The binary is the product of a toolchain the OpenWrt SDK can't easily reproduce:
an npm/Vite SPA build, embedded via //go:embed at go build time, the D9
musl-static tag set, and a UPX pass. Instead of running node + embed + UPX inside
the SDK, we build out-of-tree with scripts/build-shaterd.sh and package the
arch-matched artifact. The Makefile maps OpenWrt $(ARCH) → the artifact
(x86_64→amd64, aarch64→arm64), verifies it was staged, and $(INSTALL_BIN)s
it. Because the binary is UPX-packed, the package disables the SDK's default strip
(RSTRIP:=:) — stripping a packed executable would corrupt it.
CI order: (1) run scripts/build-shaterd.sh (builds + stages both arches into
openwrt/shaterd/files/); (2) copy openwrt/* into the SDK tree with the luci
feed installed and run make package/shaterd/compile (and the others) per target.
See openwrt-package-build-ci for SDK/feed mechanics.
3. Install on a router
Install order follows the deps (shaterd → shater-core → luci-app-shater):
opkg install shaterd_0.2.0-1_<arch>.ipk # or: apk add shaterd (25.12+)
opkg install shater-core_0.2.0-1_all.ipk
opkg install luci-app-shater_0.2.0-1_all.ipk
opkg install byedpi_0.17.3-1_<arch>.ipk # optional: ByeDPI egress
Installing from a signed feed instead:
# add the feed (customfeeds.conf / apk repositories), then:
opkg update && opkg install shater-core luci-app-shater # shaterd pulled in as a dep
4. Enable
Shater ships inert (globals disabled) so it never breaks connectivity on
install. Configure nodes/rules (via the LuCI panel or uci), then enable and apply:
uci set shater.globals.enabled=1
uci commit shater
shaterd apply # apply + arm commit-confirm on the running daemon
shaterd confirm # confirm (cancels the auto-rollback)
/etc/init.d/shater enable && /etc/init.d/shater start brings up the procd-supervised
daemon (shaterd run), which owns the engine, the inet shater data plane, policy
routing, in-process DNS, and the admin panel (default :8088). The LuCI app's
"Open panel" button mints a single-use token and hands the browser off to the panel.
5. Add the signed feed (recommended — then opkg upgrade just works)
CI (.gitea/workflows/release.yml) publishes every build as a rolling latest
Gitea release that is itself a signed opkg src/gz feed: the release holds the
.ipk for all arches, a Packages/Packages.gz index, a usign Packages.sig,
and the public key shater-feed.pub. opkg filters by Architecture, so the same
two lines work on every device (x86 testbed picks x86_64 + all; the BPI routers
pick aarch64_cortex-a53 + all).
Format: OpenWrt 24.10 (our SDK) uses opkg (
.ipk,Packages.gz, usign), so the feed issrc/gzand the trust anchor is the usign keydist/shater-feed.pub(fingerprint5ac4b177689cb8e0). apk only replaces opkg at OpenWrt 25.12 — see §6.
One-time setup on the router:
# 1) trust the feed key — the FILENAME must equal the usign key fingerprint.
wget -O /etc/opkg/keys/5ac4b177689cb8e0 \
https://git.qomar.pw/omar/shater/releases/download/latest/shater-feed.pub
# 2) add the feed (one URL serves every arch).
echo "src/gz shater https://git.qomar.pw/omar/shater/releases/download/latest" \
>> /etc/opkg/customfeeds.conf
# 3) refresh + install (shaterd is pulled in as a dependency).
opkg update
opkg install luci-app-shater # -> shater-core -> shaterd
opkg install byedpi # optional: ByeDPI desync egress
With the key installed, opkg's default check_signature 1 verifies the feed on
every opkg update; no --nocheck-signature needed. A tagged release
(vX.Y.Z) publishes the identical layout at
.../releases/download/vX.Y.Z if you prefer to pin a version instead of tracking
latest.
Updating
opkg update
opkg upgrade shaterd shater-core luci-app-shater byedpi # only our own packages
Updates are only offered when the feed's Version differs from the installed one,
so bump PKG_RELEASE (or PKG_VERSION) in the package Makefile on every
shipped change — otherwise opkg upgrade sees the same version and does nothing.
Do not opkg upgrade base/system packages from this feed; upgrade only the
four shater packages above.
6. apk feed (OpenWrt/ImmortalWrt 25.12+ — incl. BananaWRT 25.12-mtk-vendor)
OpenWrt/ImmortalWrt 25.12 replaces opkg with Alpine's apk: .apk files,
a binary packages.adb index, EC (prime256v1) keys in /etc/apk/keys/, and
effectively mandatory signatures (unsigned needs --allow-untrusted). The
package Makefiles are unchanged — the SDK release decides the format.
CI builds this lane in parallel with the opkg feed (same manual triggers:
v* tag push or workflow_dispatch): the build-apk jobs in
.gitea/workflows/release.yml compile the same 4 packages through the official
ImmortalWrt 25.12 SDK (tarballs from
downloads.immortalwrt.org/releases/25.12.1/targets/{x86/64,mediatek/filogic}/)
and publish one release per arch — rolling apk-latest-x86_64 /
apk-latest-aarch64_cortex-a53, or apk-vX.Y.Z-<arch> for a tagged version.
Per-arch (unlike the combined opkg release) because apk filenames carry no
architecture and packages are fetched relative to the packages.adb URL.
Key: apk cannot use the usign key. The apk trust anchor is the separate EC public key
dist/shater-apk.pem(generated once byci/gen-apk-key.sh; private half lives ONLY in the Gitea secretKEY_APK, the apk analog ofKEY_BUILD). Never regenerate either key — that invalidates every deployed router's trust. The usign identityshater-feed.pubkeeps signing the opkg/24.10 feed, untouched.
One-time setup on a 25.12 router (BananaWRT 25.12-mtk-vendor on the BPI-R3
mini, BPI-R4 on 25.12, or the future 25.12 VM — /etc/apk/arch picks the right
per-arch release automatically):
# 1) trust the apk feed key (any *.pem filename under /etc/apk/keys works).
wget -O /etc/apk/keys/shater-apk.pem \
"https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/shater-apk.pem"
# 2) add the repo — the line points at the packages.adb INDEX FILE itself.
echo "https://git.qomar.pw/omar/shater/releases/download/apk-latest-$(cat /etc/apk/arch)/packages.adb" \
> /etc/apk/repositories.d/shater.list
# 3) refresh + install (shaterd pulled in as a dependency).
apk update
apk add luci-app-shater # -> shater-core -> shaterd
apk add byedpi # optional: ByeDPI desync egress
Updating
apk update
apk upgrade shaterd shater-core luci-app-shater byedpi # only our own packages
Same rule as opkg: an upgrade is only offered when the feed version differs, so
bump PKG_RELEASE/PKG_VERSION on every shipped change (apk shows it as
0.2.0-r1). Pin a version instead of tracking rolling by pointing the repo line
at .../download/apk-vX.Y.Z-$(cat /etc/apk/arch)/packages.adb.
BananaWRT 25.12-mtk-vendor compatibility
The mtk-vendor channel (base: SuperKali/immortalwrt-mt798x-rebase, branch
25.12-linkup) builds target mediatek/filogic, pkg arch
aarch64_cortex-a53, and its images even point their distfeeds at vanilla
downloads.immortalwrt.org/releases/25.12-SNAPSHOT — so packages built with the
vanilla ImmortalWrt 25.12 filogic SDK install cleanly; no SuperKali-special SDK
is needed. We ship no kmods (shaterd is a static Go binary, byedpi plain C),
so the vendor 6.6 kernel is irrelevant to our packages; the kmod dependencies
of shater-core (kmod-nft-tproxy, kmod-nft-socket, plus ip-full) are
already baked into the BananaWRT mtk-vendor image (verified in its
config.buildinfo). On a self-built 25.12 image, make sure those kmods come
from the image's own kernel build.