Both routers are past opkg: mini_router runs ImmortalWrt 25.12.1 and main_router OpenWrt 25.12.0, both with apk-tools 3.0.5, and main_router has no `opkg` binary at all. The 24.10 lane was building and signing a feed no device could consume. Removed jobs `build` and `release` with the scripts only they called (ci/build-feed.sh, ci/sdk-build.sh, ci/make-index.sh, ci/install-usign.sh) and the usign trust anchor dist/shater-feed.pub. A committed public key is an instruction: it invites the old install path for a feed that is no longer produced. The key is retired, not revoked -- git history keeps it, KEY_BUILD still holds the secret half, and a usign secret contains its own public half, so the identity is reconstructible if a 24.10 device ever needs serving. D7 is marked SUPERSEDED by the new D22 rather than deleted. Separately: the rolling `apk-latest-<arch>` release was frozen at 0.2.0 from 2026-07-24 while every tag run published its versioned release correctly. The publish loop was an either/or -- `TAG=apk-latest-<arch>` when VER=latest (workflow_dispatch only), ELSE `TAG=apk-<ver>-<arch>` -- so a `v*` tag run never touched the rolling pointer. Asset replacement was never the problem; ci/gitea-release.sh already deletes before recreating. A router pinned to the rolling URL sat on 0.2.0 while `apk update` reported success: silent staleness, the failure mode this repo keeps having to close. The rolling pointer is now published on EVERY run, tag runs included, and a new assert reads the release back over the API afterwards: our three tag-versioned packages at the built version plus the index and the key must be present (exit 13), and no package asset at any other version may survive (exit 14). Same class of check as sdk-build-apk.sh's package-version assert, added for the same reason -- the previous failure mode was silent. KEY_BUILD can now be deleted from the Gitea repo secrets; nothing references it. Docs state plainly that mini_router is deliberately pinned to a versioned URL and that the hand-edit per release is the price of pinning. Known consequence: the x86_64 QEMU testbed is still OpenWrt 24.10.3 and can no longer install our packages. Its 25.12 rebuild is in flight separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4PcWfrBRyg4eWN58axaGN
454 lines
24 KiB
Bash
Executable File
454 lines
24 KiB
Bash
Executable File
#!/bin/bash
|
|
# ci/sdk-build-apk.sh — runs INSIDE a plain `debian:bookworm` container and
|
|
# drives the **ImmortalWrt 25.12 apk-SDK** (tarball) to build the 4 shater
|
|
# packages as `.apk` and produce a SIGNED per-arch apk repo dir:
|
|
# $OUT/*.apk + $OUT/packages.adb (EC-signed) + $OUT/shater-apk.pem
|
|
#
|
|
# WHY a plain debian container + SDK tarball (not an `immortalwrt/sdk` image):
|
|
# Docker Hub has NO immortalwrt/sdk tag for mediatek-filogic 25.12 (checked
|
|
# 2026-07: x86_64-openwrt-25.12.1 exists, mediatek-filogic stops at 24.10), and
|
|
# the target fleet (BananaWRT 25.12-mtk-vendor = ImmortalWrt 25.12 base, its
|
|
# distfeeds even point at downloads.immortalwrt.org/releases/25.12-SNAPSHOT) is
|
|
# ImmortalWrt — so we extract the official ImmortalWrt SDK tarball ourselves.
|
|
#
|
|
# The OpenWrt buildsystem refuses to run as root, so the SDK build itself runs
|
|
# as an unprivileged `build` user created here.
|
|
#
|
|
# Env (required): ARCH, REPO, OUT, SDK_URL.
|
|
# Env (optional): KEY_APK — EC private key PEM; if set, packages.adb is signed
|
|
# (embedded adb signature, verified via /etc/apk/keys on the
|
|
# router). If unset -> UNSIGNED index (apk needs
|
|
# --allow-untrusted; do not ship that).
|
|
set -eu
|
|
ARCH="${ARCH:?ARCH env required}"
|
|
REPO="${REPO:?REPO env required}"
|
|
OUT="${OUT:?OUT env required}"
|
|
SDK_URL="${SDK_URL:?SDK_URL env required}"
|
|
|
|
echo "[apk-sdk] arch=$ARCH repo=$REPO out=$OUT"
|
|
echo "[apk-sdk] sdk=$SDK_URL"
|
|
# Package version derived from the git tag by ci/version.sh (bug B4). Forwarded
|
|
# to the unprivileged build user on the `su` line at the bottom of this file;
|
|
# openwrt/{shaterd,shater-core,luci-app-shater}/Makefile pick it up from the
|
|
# environment. byedpi keeps upstream ByeDPI's own version (see its Makefile).
|
|
echo "[apk-sdk] package version: ${SHATER_PKG_VERSION:-<unset -> Makefile fallback>}-r${SHATER_PKG_RELEASE:-?}"
|
|
test -f "$REPO/openwrt/shaterd/Makefile" || {
|
|
echo "[apk-sdk] ERROR: feed not mounted ($REPO/openwrt/shaterd/Makefile missing)"; ls -la "$REPO" || true; exit 9; }
|
|
|
|
# The prebuilt shaterd artifact must already be staged for this arch
|
|
# (artifact-order contract — scripts/build-shaterd.sh runs first).
|
|
case "$ARCH" in
|
|
x86_64) sfx=amd64 ;;
|
|
aarch64_cortex-a53) sfx=arm64 ;;
|
|
*) echo "[apk-sdk] ERROR: unsupported ARCH '$ARCH'"; exit 2 ;;
|
|
esac
|
|
test -f "$REPO/openwrt/shaterd/files/shaterd-$sfx.upx" || {
|
|
echo "[apk-sdk] ERROR: openwrt/shaterd/files/shaterd-$sfx.upx not staged."
|
|
echo " scripts/build-shaterd.sh must run on the runner before the SDK build."; exit 3; }
|
|
|
|
# Signing sanity: a signed feed must also ship its public key.
|
|
if [ -n "${KEY_APK:-}" ] && [ ! -s "$REPO/dist/shater-apk.pem" ]; then
|
|
echo "[apk-sdk] ERROR: KEY_APK is set but dist/shater-apk.pem is missing —"
|
|
echo " run ci/gen-apk-key.sh and commit the PUBLIC key (git add -f)."; exit 4
|
|
fi
|
|
|
|
# --- 0) host deps for the OpenWrt/ImmortalWrt SDK on debian:bookworm ---------
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
# Reuse downloaded .deb archives across runs: the runner restores $APT_CACHE
|
|
# (workspace .cache/apt, shared via --volumes-from) from actions/cache. Only
|
|
# the download is cached — dpkg still installs; the debian image's docker-clean
|
|
# hook (auto-deletes archives after install) must go for the cache to fill.
|
|
if [ -n "${APT_CACHE:-}" ]; then
|
|
mkdir -p "$APT_CACHE/archives/partial"
|
|
chmod -R a+rwX "$APT_CACHE" 2>/dev/null || true
|
|
echo "Dir::Cache::archives \"$APT_CACHE/archives\";" > /etc/apt/apt.conf.d/99shater-cache
|
|
rm -f /etc/apt/apt.conf.d/docker-clean
|
|
fi
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends \
|
|
build-essential file gawk gettext git libncurses-dev libssl-dev \
|
|
python3 python3-distutils python3-setuptools \
|
|
rsync unzip wget xz-utils zlib1g-dev zstd ca-certificates >/dev/null
|
|
echo "[apk-sdk] host deps installed"
|
|
|
|
# --- 1) fetch + extract the ImmortalWrt SDK tarball --------------------------
|
|
useradd -m -s /bin/bash build
|
|
SDKDIR=/home/build/sdk
|
|
mkdir -p "$SDKDIR"
|
|
if [ -n "${SDK_TAR:-}" ] && [ -s "$SDK_TAR" ]; then
|
|
# Normal CI path: ci/build-feed-apk.sh already fetched it on the runner
|
|
# (cache / mirror / retried upstream — ci/fetch-sdk.sh). Do NOT delete it:
|
|
# the tarball in the workspace IS the cache for the next run.
|
|
echo "[apk-sdk] extracting pre-fetched SDK tarball: $SDK_TAR"
|
|
tar --zstd -xf "$SDK_TAR" -C "$SDKDIR" --strip-components=1
|
|
else
|
|
# Standalone fallback. --timeout=60 covers a STALLED stream too (wget read
|
|
# timeout), so a hung downloads.immortalwrt.org transfer aborts + retries
|
|
# instead of wedging the whole build.
|
|
echo "[apk-sdk] no SDK_TAR staged — downloading in-container (fallback)"
|
|
wget -O /tmp/sdk.tar.zst --timeout=60 --tries=3 --waitretry=10 "$SDK_URL"
|
|
tar --zstd -xf /tmp/sdk.tar.zst -C "$SDKDIR" --strip-components=1
|
|
rm -f /tmp/sdk.tar.zst
|
|
fi
|
|
test -x "$SDKDIR/scripts/feeds" || { echo "[apk-sdk] ERROR: SDK layout unexpected (scripts/feeds missing)"; exit 5; }
|
|
chown -R build:build /home/build
|
|
|
|
# Private key: readable only by the build user, outside the shared workspace.
|
|
KEYFILE=""
|
|
if [ -n "${KEY_APK:-}" ]; then
|
|
KEYFILE=/home/build/shater-apk.key
|
|
umask 077
|
|
printf '%s\n' "$KEY_APK" > "$KEYFILE"
|
|
chown build:build "$KEYFILE"
|
|
umask 022
|
|
fi
|
|
|
|
mkdir -p "$OUT"; chmod 0777 "$OUT"
|
|
|
|
# --- 2) SDK build + index as the unprivileged user ---------------------------
|
|
cat > /home/build/inner.sh <<'INNER'
|
|
set -eu
|
|
export HOME=/home/build
|
|
cd "$SDKDIR"
|
|
|
|
# Register this repo's openwrt/ as a src-link feed named `shater` (absolute
|
|
# path required).
|
|
cp -f feeds.conf.default feeds.conf
|
|
grep -q '^src-link shater ' feeds.conf || echo "src-link shater $REPO/openwrt" >> feeds.conf
|
|
|
|
# Persistent feeds checkouts: $FEEDS_CACHE (workspace dir, actions/cache-
|
|
# persisted, visible via --volumes-from) replaces the fresh SDK's empty feeds/
|
|
# dir, so `feeds update` git-fetches deltas instead of re-cloning the
|
|
# ImmortalWrt feeds every run. Update always checks out feeds.conf's pinned
|
|
# revisions; on any failure with cached checkouts the cache is wiped and the
|
|
# update retried with fresh clones — a stale cache can never wedge the build.
|
|
if [ -n "${FEEDS_CACHE:-}" ] && mkdir -p "$FEEDS_CACHE" 2>/dev/null; then
|
|
rm -rf feeds
|
|
ln -s "$FEEDS_CACHE" feeds
|
|
echo "[apk-sdk] feeds/ -> $FEEDS_CACHE (persistent cache)"
|
|
fi
|
|
echo "[apk-sdk] feeds update -a"
|
|
if ! ./scripts/feeds update -a; then
|
|
[ -L feeds ] || { echo "[apk-sdk] ERROR: feeds update failed"; exit 8; }
|
|
echo "[apk-sdk] WARNING: feeds update failed on cached checkouts — wiping cache, cloning fresh"
|
|
find "$FEEDS_CACHE" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
|
|
./scripts/feeds update -a
|
|
fi
|
|
echo "[apk-sdk] feeds install (prefer shater feed)"
|
|
./scripts/feeds install -p shater shaterd shater-core byedpi luci-app-shater
|
|
|
|
# --- strip the SDK's generated per-package `default m` blocks ----------------
|
|
# Run 60 settled the question that runs 58 and 59 left open. Writing an explicit
|
|
# `# CONFIG_PACKAGE_kmod-x is not set` for all 1126 of them and re-running
|
|
# defconfig deselected exactly nothing: the count came back 1078, unchanged.
|
|
# Meanwhile the very same explicit form DID stick for CONFIG_ALL/ALL_KMODS/
|
|
# ALL_NONSHARED. The difference is prompts. kconfig only honours a user value for
|
|
# a symbol that has one (sym_calc_value ignores S_DEF_USER for a promptless
|
|
# symbol and falls back to its `default`), and the ALL* symbols carry prompts in
|
|
# the SDK's own Config.in while these generated blocks are bare:
|
|
#
|
|
# config PACKAGE_kmod-mlx5-core
|
|
# tristate
|
|
# default m
|
|
#
|
|
# So no value we write into .config can ever turn them off — the fix has to
|
|
# remove the `default m` itself. That is what this does: drop every generated
|
|
# `config PACKAGE_*` block from the SDK's Config-build.in before the first
|
|
# defconfig. Nothing is lost by it — these blocks only replay which packages the
|
|
# BUILDBOT happened to build; the packages themselves are still declared, with
|
|
# prompts, by the package tree (tmp/.config-package.in), which is what makes our
|
|
# four selectable and what `select` acts on. KERNEL_*/LIBC/TOOLCHAIN blocks are
|
|
# left untouched, so the SDK still reproduces its own toolchain settings.
|
|
CB=$(find . -maxdepth 2 -name 'Config-build.in' -print -quit 2>/dev/null || true)
|
|
if [ -n "$CB" ] && command -v perl >/dev/null 2>&1; then
|
|
pkg_before=$(grep -c '^config PACKAGE_' "$CB" || true)
|
|
# Paragraph-wise delete: a block is `config PACKAGE_x`, its indented body, and
|
|
# the blank line that ends it. Anchored per-line (/m) so nothing else matches.
|
|
perl -0777 -pi -e 's/^config PACKAGE_\S+\n(?:[ \t]+\S[^\n]*\n)+\n//gm' "$CB"
|
|
pkg_after=$(grep -c '^config PACKAGE_' "$CB" || true)
|
|
echo "[apk-sdk] $CB: stripped $((pkg_before - pkg_after)) generated PACKAGE default blocks ($pkg_before -> $pkg_after)"
|
|
else
|
|
echo "[apk-sdk] WARNING: no Config-build.in found (or no perl) — per-package"
|
|
echo "[apk-sdk] 'default m' blocks stay; the kmod tripwire will catch it"
|
|
fi
|
|
|
|
# --- .config: turn OFF the SDK's mass-select defaults ------------------------
|
|
# Symptom (v0.2.2, and still v0.2.3 run 58): the SDK ran `apk mkpkg` on ~1100
|
|
# kmod-* packages — mlx5, amdgpu, ata, isdn, none of which we ship — and died
|
|
# with `Disk quota exceeded` on the runner's 64 GB ZFS quota. Our kmod deps pull
|
|
# in `package/kernel/linux/compile`, which packs every module marked =m.
|
|
#
|
|
# Why they are =m has nothing to do with anything we write here. An OpenWrt SDK
|
|
# carries its OWN top-level Config.in (target/sdk/files/Config.in), and it reads:
|
|
#
|
|
# config ALL_NONSHARED
|
|
# bool "Select all target specific packages by default"
|
|
# default ALL
|
|
# config ALL_KMODS
|
|
# bool "Select all kernel module packages by default"
|
|
# default ALL
|
|
# config ALL
|
|
# bool "Select all userspace packages by default"
|
|
# default y <-- y, not n, and ONLY inside the SDK
|
|
#
|
|
# In the main tree those three default to n; the SDK flips ALL to y so that
|
|
# `make world` in a bare SDK builds something useful. So `make defconfig` on ANY
|
|
# .config — empty or not — selects the entire kernel. This is stock OpenWrt, not
|
|
# an ImmortalWrt quirk: openwrt/openwrt's target/sdk/files/Config.in is identical.
|
|
# (It also means the reference we copied, Slava-Shchipunov/awg-openwrt, builds
|
|
# every kmod too — it just never hits a disk quota on GitHub's runners.)
|
|
#
|
|
# Fix: state all three explicitly. They carry prompts in the SDK's Config.in, so
|
|
# they are user-settable and an explicit value beats the `default`. Note the FORM:
|
|
# kconfig writes a false bool as `# CONFIG_X is not set` and `CONFIG_X=n` is not
|
|
# reliably honoured, so `is not set` is the only form used here. All three are set
|
|
# rather than just the root `ALL`, so this keeps working whichever symbol a future
|
|
# SDK makes the root of the chain.
|
|
# Stash anything the SDK shipped (see below — today there is nothing) and start
|
|
# from a known-empty file, so what we build here is exactly what we intended.
|
|
if [ -s .config ]; then mv -f .config .config.sdk; fi
|
|
: > .config
|
|
for s in ALL ALL_KMODS ALL_NONSHARED; do
|
|
echo "# CONFIG_$s is not set" >> .config
|
|
done
|
|
|
|
# About that stash: an SDK tarball ships NO top-level .config (run 58 logged
|
|
# `grep: .config: No such file or directory` — the only `.config` inside the
|
|
# tarball is the prebuilt KERNEL's, under the linux dir). This is also why the
|
|
# first version of this fix was aimed at the wrong thing: there was never a
|
|
# buildbot .config here to append to. Nothing needs carrying over from it either,
|
|
# because
|
|
# target/sdk/Makefile bakes the buildbot's non-package settings — every
|
|
# CONFIG_KERNEL_* included — into the SDK's generated Config-build.in as kconfig
|
|
# `default`s (target/sdk/convert-config.pl). defconfig therefore reproduces the
|
|
# exact toolchain/kernel settings the SDK was built with, on its own; an earlier
|
|
# attempt to copy those lines by hand was redundant and is gone.
|
|
# Should a future SDK start shipping a .config, this keeps the two things that
|
|
# would then be worth honouring — the target identity and the package format —
|
|
# and still lets the lines above override the mass-select.
|
|
if [ -s .config.sdk ]; then
|
|
echo "[apk-sdk] SDK shipped a .config — carrying over target identity + format:"
|
|
grep -E '^CONFIG_TARGET_[a-z0-9_]+=y$|^CONFIG_TARGET_(BOARD|SUBTARGET|ARCH_PACKAGES)=|^CONFIG_USE_APK=' \
|
|
.config.sdk | tee -a .config | sed 's/^/[apk-sdk] /' || true
|
|
fi
|
|
|
|
for p in shaterd shater-core byedpi luci-app-shater; do
|
|
echo "CONFIG_PACKAGE_$p=m" >> .config
|
|
done
|
|
# Route source downloads through OpenWrt's fast CDN mirror FIRST. Some upstreams
|
|
# (e.g. sourceware.org for elfutils) intermittently stall mid-transfer, and curl's
|
|
# --connect-timeout doesn't cover a stalled stream, so the SDK download hangs the
|
|
# whole build. LOCALMIRROR is tried before each package's own PKG_SOURCE_URL. (lx CI)
|
|
echo 'CONFIG_LOCALMIRROR="https://sources.cdn.openwrt.org"' >> .config
|
|
# Persistent dl/ across runs: $DL_DIR is a workspace dir the runner restores via
|
|
# actions/cache. Correctness-safe: the buildroot verifies PKG_HASH on every file
|
|
# already in dl/ and re-downloads on mismatch, so a stale cache can never leak a
|
|
# wrong source into the build.
|
|
if [ -n "${DL_DIR:-}" ]; then
|
|
echo "CONFIG_DOWNLOAD_FOLDER=\"$DL_DIR\"" >> .config
|
|
fi
|
|
echo "[apk-sdk] defconfig"
|
|
make defconfig >/dev/null
|
|
|
|
# --- second pass: deselect the kernel, keep only what our packages select -----
|
|
# Turning ALL/ALL_KMODS/ALL_NONSHARED off (above) provably worked — run 59 shows
|
|
# all three as `is not set` after defconfig — and changed the kmod count by
|
|
# exactly zero, 1078 both times. The kmods are not selected through ALL_KMODS at
|
|
# all. They are selected one by one, and here is where from:
|
|
#
|
|
# target/sdk/Makefile:
|
|
# ./convert-config.pl $(TOPDIR)/.config > $(SDK_BUILD_DIR)/Config-build.in
|
|
#
|
|
# The SDK's Config-build.in is GENERATED from the buildbot's .config — a config
|
|
# in which ALL_KMODS=y had already expanded into a `CONFIG_PACKAGE_kmod-*=m` line
|
|
# per module. convert-config.pl turns every `CONFIG_X=<val>` line into a kconfig
|
|
# symbol carrying an unconditional `default <val>`; its `next if
|
|
# /^(# )?CONFIG_PACKAGE/` filter sits in the `else` branch, which a line with an
|
|
# `=` in it never reaches. So the SDK ships, verbatim, 1078 blocks of:
|
|
#
|
|
# config PACKAGE_kmod-mlx5-core
|
|
# tristate
|
|
# default m
|
|
#
|
|
# Nothing there consults ALL_KMODS, which is why switching it off was inert.
|
|
#
|
|
# Fix: give those symbols an explicit user value. We cannot do it before the
|
|
# first defconfig — the list of names only exists once kconfig has expanded the
|
|
# tree — so this is a second pass: rewrite every selected kmod to `is not set`
|
|
# and re-run defconfig. Two kconfig rules make the result exactly what we want,
|
|
# and both are already demonstrated in our own logs:
|
|
# * an explicit value in .config beats a `default` (this is precisely why the
|
|
# `# CONFIG_ALL* is not set` lines survived defconfig in run 59), so the
|
|
# ~1078 kmods we do not need stay off;
|
|
# * `select` is a reverse dependency, OR-ed into the symbol's value AFTER the
|
|
# user value in sym_calc_value(), so it cannot be overridden by an explicit
|
|
# `n`. shater-core's `DEPENDS:=+kmod-nft-tproxy +kmod-nft-socket` becomes
|
|
# `select PACKAGE_kmod-nft-tproxy` (scripts/package-metadata.pl: a `+` flag
|
|
# sets `$m = "select"`, and it re-emits the dependency's own depends too, so
|
|
# transitive kmods follow). Those come back on their own.
|
|
# Net effect: we build the handful of kmods our packages actually pull in.
|
|
#
|
|
# Rejected alternatives:
|
|
# * limiting what `package/kernel/linux/compile` packs — that target has no
|
|
# such knob; it iterates the selected set, so the selection IS the knob;
|
|
# * `package/kernel/linux/clean` + a targeted build — the kernel package would
|
|
# simply be rebuilt in full as a dependency of shater-core, same cost;
|
|
# * copying OpenWrt's own feed CI (openwrt/gh-action-sdk) — it does nothing
|
|
# about this; it just runs `make defconfig` and builds. Its one disk-related
|
|
# setting, CONFIG_AUTOREMOVE=y, is already the SDK's default;
|
|
# * editing the SDK's generated Config-build.in to strip the offending blocks —
|
|
# it would work, but it means parsing a generated kconfig file by hand and a
|
|
# format change would corrupt it silently. The two-pass approach uses only
|
|
# kconfig's documented semantics and leaves the evidence in .config.
|
|
kmods_all=$(grep -c '^CONFIG_PACKAGE_kmod-[^=]*=[my]$' .config || true)
|
|
if [ "$kmods_all" -gt 0 ]; then
|
|
echo "[apk-sdk] deselecting $kmods_all kmod packages, then defconfig again"
|
|
sed -i -E 's/^CONFIG_(PACKAGE_kmod-[^=]*)=[my]$/# CONFIG_\1 is not set/' .config
|
|
make defconfig >/dev/null
|
|
fi
|
|
|
|
# --- post-defconfig sanity + disk-cost readout -------------------------------
|
|
# A failed run leaves a ~27 MB log; digging the cause out of it is miserable, so
|
|
# print the handful of numbers that decide whether this run survives the
|
|
# runner's disk quota BEFORE anything is compiled.
|
|
kmods=$(grep -c '^CONFIG_PACKAGE_kmod.*=m' .config || true)
|
|
echo "[apk-sdk] target: board=$(sed -n 's/^CONFIG_TARGET_BOARD=//p' .config)" \
|
|
"subtarget=$(sed -n 's/^CONFIG_TARGET_SUBTARGET=//p' .config)" \
|
|
"arch_packages=$(sed -n 's/^CONFIG_TARGET_ARCH_PACKAGES=//p' .config)"
|
|
echo "[apk-sdk] kmod packages selected (=m): $kmods"
|
|
# Proof the mass-select stayed off: these three must come back out of defconfig
|
|
# as `is not set`. If any reads `=y`, the SDK's `default ALL`/`default y` won and
|
|
# the kmod count above will be in the four digits.
|
|
echo "[apk-sdk] mass-select symbols after defconfig:"
|
|
grep -E '^(# )?CONFIG_ALL(_KMODS|_NONSHARED)?[ =]' .config | sed 's/^/[apk-sdk] /' || true
|
|
# After the second pass the only kmods left are the ones shater-core's
|
|
# `DEPENDS:=+kmod-nft-tproxy +kmod-nft-socket` turns into kconfig `select`s, plus
|
|
# whatever those select in turn — a handful. Worth printing verbatim while the
|
|
# list is short. A count of 0 is NOT fatal: those kmods ship in the router's own
|
|
# base feed, so apk resolves them there; but it would mean the selects did not
|
|
# fire, and that is something we want to see in the log rather than guess at.
|
|
if [ "$kmods" -le 30 ]; then
|
|
grep '^CONFIG_PACKAGE_kmod.*=m' .config | sed 's/^/[apk-sdk] /' || true
|
|
fi
|
|
# The two cache knobs are written before the first defconfig and have to survive
|
|
# both of them — losing DOWNLOAD_FOLDER silently costs us the dl/ cache, and
|
|
# losing LOCALMIRROR brings back the sourceware.org stalls. Cheap to just look.
|
|
echo "[apk-sdk] cache settings after defconfig:"
|
|
grep -E '^CONFIG_(LOCALMIRROR|DOWNLOAD_FOLDER)=' .config | sed 's/^/[apk-sdk] /' || true
|
|
echo "[apk-sdk] our packages after defconfig:"
|
|
grep -E '^CONFIG_PACKAGE_(shaterd|shater-core|byedpi|luci-app-shater)=' .config \
|
|
| sed 's/^/[apk-sdk] /' || true
|
|
|
|
# Each of our 4 must have SURVIVED defconfig. If kconfig dropped one, it is
|
|
# because a symbol it `select`s (a DEPENDS entry) does not exist in the installed
|
|
# feeds — with the old append-everything .config that was masked by the SDK
|
|
# pre-selecting half the distro. `make package/<p>/compile` would then die with a
|
|
# cryptic "No rule to make target", far from the real cause.
|
|
for p in shaterd shater-core byedpi luci-app-shater; do
|
|
grep -q "^CONFIG_PACKAGE_$p=m" .config || {
|
|
echo "[apk-sdk] ERROR: $p is NOT selected after defconfig."
|
|
echo " kconfig dropped it -> one of its DEPENDS is missing from the"
|
|
echo " installed feeds (check the 'feeds install' step above)."; exit 10; }
|
|
done
|
|
|
|
# Only our two nft kmods (+ whatever they themselves depend on) have any business
|
|
# being selected here — a dozen at the very most. A count in the hundreds means an
|
|
# ALL_KMODS-style mass-select crept back in, and the run would spend ~40 min
|
|
# packing the kernel before dying on `Disk quota exceeded`. Fail now instead.
|
|
[ "$kmods" -le 200 ] || {
|
|
echo "[apk-sdk] ERROR: $kmods kmod packages selected — that is the whole kernel."
|
|
echo " Aborting before this fills the runner's disk. Two causes are"
|
|
echo " possible, and the lines below tell them apart:"
|
|
echo " (a) the mass-select is back on -> a CONFIG_ALL* line reads =y;"
|
|
echo " (b) the second pass did not take -> ALL* are 'is not set' but the"
|
|
echo " kmods returned anyway, i.e. the per-kmod 'default m' from the"
|
|
echo " SDK's generated Config-build.in outlived our explicit 'n'."
|
|
grep -E '^(# )?CONFIG_ALL(_KMODS|_NONSHARED)?[ =]' .config | sed 's/^/ /' || true
|
|
echo " first few kmods still selected:"
|
|
grep -m5 '^CONFIG_PACKAGE_kmod.*=m' .config | sed 's/^/ /' || true
|
|
exit 11; }
|
|
|
|
for p in shaterd shater-core byedpi luci-app-shater; do
|
|
echo "[apk-sdk] === build $p ==="
|
|
make "package/$p/compile" V=s -j"$(nproc)"
|
|
done
|
|
|
|
# What the build actually cost on disk. The runner's 64 GB ZFS quota is the
|
|
# binding constraint on this lane, so record it while the tree still exists.
|
|
echo "[apk-sdk] disk usage after compile:"
|
|
du -sh build_dir staging_dir bin 2>/dev/null || true
|
|
df -h /home/build || true
|
|
|
|
# A 25.12 apk-SDK must emit .apk — finding only .ipk means a wrong SDK was fed in.
|
|
anyapk=$(find bin -type f -name '*.apk' | wc -l)
|
|
[ "$anyapk" -gt 0 ] || {
|
|
echo "[apk-sdk] ERROR: no .apk produced under bin/ (wrong/older SDK? found $(find bin -type f -name '*.ipk' | wc -l) .ipk)";
|
|
find bin -maxdepth 4 -type d || true; exit 6; }
|
|
# Collect ONLY our 4 packages' .apk (apk filenames carry NO arch:
|
|
# `<name>-<ver>-r<rel>.apk`). NOT a blanket `*.apk` copy — the SDK bin/ can hold
|
|
# prebuilt base/kmod .apk that would bloat the index and be signed under our key.
|
|
found=0
|
|
for p in shaterd shater-core byedpi luci-app-shater; do
|
|
for a in $(find bin -type f -name "${p}-*.apk"); do
|
|
cp -f "$a" "$OUT/"; found=$((found+1))
|
|
done
|
|
done
|
|
[ "$found" -ge 4 ] || { echo "[apk-sdk] ERROR: expected >=4 of OUR .apk, collected $found"; echo "[apk-sdk] (all .apk under bin/:)"; find bin -type f -name '*.apk' | head -20; exit 6; }
|
|
echo "[apk-sdk] collected $found of our .apk"
|
|
|
|
# --- assert the tag-derived version actually reached the packages -------------
|
|
# B4's failure mode is a wrong-but-plausible version shipping silently, so the
|
|
# env -> make hand-off is verified, not trusted: each of our three tag-versioned
|
|
# packages must be named `<name>-<ver>-r<rel>.apk`. byedpi is excluded on purpose
|
|
# (it carries upstream ByeDPI's own version). This runs BEFORE `apk mkndx`, so a
|
|
# stale version can never even reach the index.
|
|
if [ -n "${SHATER_PKG_VERSION:-}" ] && [ -n "${SHATER_PKG_RELEASE:-}" ]; then
|
|
want="${SHATER_PKG_VERSION}-r${SHATER_PKG_RELEASE}"
|
|
for p in shaterd shater-core luci-app-shater; do
|
|
[ -f "$OUT/${p}-${want}.apk" ] || {
|
|
echo "[apk-sdk] ERROR: $p was not built as version '$want'."
|
|
echo " SHATER_PKG_VERSION/SHATER_PKG_RELEASE did not reach the package"
|
|
echo " Makefile — the build would have shipped a stale version (bug B4)."
|
|
echo "[apk-sdk] collected:"; ls -1 "$OUT" | sed 's/^/ /'
|
|
exit 12; }
|
|
done
|
|
echo "[apk-sdk] version check OK — our 3 packages are $want"
|
|
fi
|
|
|
|
# --- index + sign: exactly how the OpenWrt 25.12 buildsystem does it ---------
|
|
# apk mkndx --root T --keys-dir T [--sign key] --allow-untrusted \
|
|
# --output packages.adb *.apk
|
|
# (--allow-untrusted lets it index our unsigned member .apk; trust comes from
|
|
# the SIGNED INDEX — same model as the official feeds, which stopped signing
|
|
# individual packages.)
|
|
APKTOOL="$SDKDIR/staging_dir/host/bin/apk"
|
|
test -x "$APKTOOL" || { echo "[apk-sdk] ERROR: SDK host apk tool missing ($APKTOOL)"; exit 7; }
|
|
T="$(mktemp -d)"
|
|
mkdir -p "$T/keys"
|
|
[ -s "$REPO/dist/shater-apk.pem" ] && cp -f "$REPO/dist/shater-apk.pem" "$T/keys/"
|
|
cd "$OUT"
|
|
if [ -n "${KEYFILE:-}" ] && [ -s "${KEYFILE:-/nonexistent}" ]; then
|
|
"$APKTOOL" mkndx --root "$T" --keys-dir "$T/keys" --allow-untrusted \
|
|
--sign "$KEYFILE" --output packages.adb ./*.apk
|
|
echo "[apk-sdk] packages.adb SIGNED (embedded adb signature)"
|
|
else
|
|
"$APKTOOL" mkndx --root "$T" --keys-dir "$T/keys" --allow-untrusted \
|
|
--output packages.adb ./*.apk
|
|
echo "[apk-sdk] WARNING: no KEY_APK -> UNSIGNED packages.adb (apk on the"
|
|
echo "[apk-sdk] router will refuse it without --allow-untrusted)"
|
|
fi
|
|
# prove the index parses; show what a router will see
|
|
"$APKTOOL" adbdump packages.adb | head -n 40 || true
|
|
|
|
# ship the public key next to the index so the release is self-contained
|
|
[ -s "$REPO/dist/shater-apk.pem" ] && cp -f "$REPO/dist/shater-apk.pem" "$OUT/"
|
|
INNER
|
|
chmod 0644 /home/build/inner.sh
|
|
|
|
su build -s /bin/bash -c \
|
|
"ARCH='$ARCH' REPO='$REPO' OUT='$OUT' SDKDIR='$SDKDIR' KEYFILE='${KEYFILE:-}' DL_DIR='${DL_DIR:-}' FEEDS_CACHE='${FEEDS_CACHE:-}' SHATER_PKG_VERSION='${SHATER_PKG_VERSION:-}' SHATER_PKG_RELEASE='${SHATER_PKG_RELEASE:-}' bash /home/build/inner.sh"
|
|
|
|
chmod -R a+rwX "$OUT" 2>/dev/null || true
|
|
echo "[apk-sdk] OK arch=$ARCH — apk feed dir:"
|
|
ls -l "$OUT"
|