Files
shater/docs-shater/INSTALL.md
T
omarandClaude Opus 4.8 cd598b0fe2 feat(ci): add apk (ImmortalWrt/BananaWRT 25.12) release lane
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>
2026-07-23 16:11:23 +03:00

11 KiB
Raw Permalink Blame History

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:

  1. Builds the admin SPA — cd panel && npm ci && npm run build (Vite → panel/dist).
  2. Copies panel/dist/* into shater/panel/webroot/, so //go:embed all:webroot bakes the real SPA into the binary (not the "SPA not embedded" placeholder).
  3. Cross-builds, for each of {amd64, arm64}, with the D9 musl-static router tag set (CGO_ENABLED=0 GOOS=linux), stripped + trimmed, into dist/shaterd-<arch> (uncompressed, kept for debugging).
  4. UPX --lzma --best (D10) → dist/shaterd-<arch>.upx (~42 MB → ~8–11 MB).
  5. Stages dist/shaterd-<arch>.upx into openwrt/shaterd/files/ for the package.
  6. Prints a size table + a per-arch static check (must be ET_EXEC, no PT_INTERP).

Arg / env:

  • VERSION — stamped into constant.Version. Resolution: positional arg → $SHATER_VERSION → git describe --tags → v0.2.0-dev.
  • --fast — skip npm ci when panel/node_modules already exists.
  • UPX=/path/to/upx — override the UPX binary (default upx on PATH). UPX is cross-arch, so one host packs both the amd64 and aarch64 ELFs. (Note: UPX also reads $UPX as its own options — the script unsets 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.

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 is src/gz and the trust anchor is the usign key dist/shater-feed.pub (fingerprint 5ac4b177689cb8e0). 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 by ci/gen-apk-key.sh; private half lives ONLY in the Gitea secret KEY_APK, the apk analog of KEY_BUILD). Never regenerate either key — that invalidates every deployed router's trust. The usign identity shater-feed.pub keeps 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.