Files
shater/ci/sdk-build.sh
T
omarandClaude Opus 5 a8f2b0f068 ci: derive package versions from the git tag (B4)
PKG_VERSION/PKG_RELEASE were hand-written literals nobody bumped, so
v0.2.2 … v0.2.6 all shipped as `shaterd 0.2.0-r3` with different binaries
inside (v0.2.6's ELF is 5 491 616 B against r2's 5 488 336 B). Both opkg
and apk offer an upgrade only when the feed's version string differs from
the installed one, so `apk update` saw nothing new and the routers could
not be updated through the normal path at all.

ci/version.sh is now the single source of truth. It derives the version
from `git describe`:

    tag `vX.Y.Z`   -> PKG_VERSION=X.Y.Z  PKG_RELEASE=1
    off-tag build  -> nearest tag + PKG_RELEASE=<commits since it> + 1
    no tag/no git  -> 0.0.0-r1 (below everything ever published)

Ordering verified with the real tools, not from memory — apk-tools 3.0.3
(`apk version -t`) and opkg 38eccbb1 (`opkg compare-versions`) agree that
0.2.0-r3 < 0.2.6-r2 < 0.2.6-r10 < 0.2.6-r12 < 0.2.7-r1 < 0.3.0-r1, so a
release always outranks the rolling builds that preceded it and rolling
builds grow monotonically between releases.

The value travels as SHATER_PKG_VERSION/SHATER_PKG_RELEASE in the SDK
build environment of BOTH lanes; the Makefiles keep a literal fallback so
a manual/offline build still works with no CI and no git. Because the
hand-off crosses docker, `su` and make's env import, ci/sdk-build.sh and
ci/sdk-build-apk.sh now ASSERT that the produced .ipk/.apk really carries
that version — the B4 failure mode was a stale version shipping silently,
and that can no longer happen quietly.

The binary agrees with the package: scripts/build-shaterd.sh takes
constant.Version from the same ci/version.sh (vX.Y.Z-rR[-g<sha>]) instead
of its own `git describe`, and the workflow computes it once per job.
Both build jobs now check out with fetch-depth: 0 — `git describe` needs
tags and ancestry, which the default shallow checkout has neither of.

byedpi is deliberately left alone: PKG_VERSION:=0.17.3 is upstream
ByeDPI's own version, what PKG_HASH pins and what tells an operator which
ByeDPI is installed. Stamping our tag on it would also be a downgrade —
every comparator reads 0.2.7 < 0.17.3 (component-wise, 2 < 17), verified.

Docs: INSTALL.md gains §2.1 (the scheme + the ordering evidence), and the
update sections of §5/§6 now explicitly warn against a bare `opkg upgrade`
/ `apk upgrade` and give the targeted form instead, quoting apk-tools 3:
"If list of packages is provided, only those packages are upgraded along
with needed dependencies". README.md and the release bodies match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 12:32:38 +03:00

144 lines
7.6 KiB
Bash

#!/bin/sh
# Runs INSIDE an `openwrt/sdk:<target>-<ver>` container (CWD = SDK root
# /builder). The job's workspace is shared into this container via
# `docker run --volumes-from`, so the repo is visible at $REPO and output goes
# to $OUT (a dir under the repo, hence also visible to the runner afterwards).
#
# Unlike Shater v0.1 (which compiled ONLY xrayctl in the SDK and hand-packed the
# pure-data packages with tar), v0.2 builds ALL FOUR packages the canonical way,
# via the SDK feed + `make package/<p>/compile`:
#
# shaterd prebuilt binary — Build/Compile only VALIDATES that
# openwrt/shaterd/files/shaterd-<amd64|arm64>.upx was staged
# by scripts/build-shaterd.sh on the runner BEFORE this ran.
# (arch-specific .ipk: RSTRIP/STRIP disabled — packed ELF.)
# shater-core PKGARCH=all data glue (procd init, sysctl, uci-defaults).
# luci-app-shater PKGARCH=all LuCI thin launcher — its Makefile does
# `include $(TOPDIR)/feeds/luci/luci.mk`, so the `luci` feed
# MUST be updated first (that is what creates feeds/luci/luci.mk).
# byedpi arch-specific C — the SDK cross-compiles ciadpi from the
# upstream tarball (needs network for PKG_SOURCE_URL).
#
# Env (required): ARCH, REPO, OUT.
set -eu
ARCH="${ARCH:?ARCH env required}"
REPO="${REPO:?REPO env required}"
OUT="${OUT:?OUT env required}"
mkdir -p "$OUT"
echo "[sdk] arch=$ARCH repo=$REPO out=$OUT"
# Package version, derived from the git tag by ci/version.sh and handed in by
# ci/build-feed.sh. openwrt/{shaterd,shater-core,luci-app-shater}/Makefile read
# these straight out of the environment ($(if $(SHATER_PKG_VERSION),...)); make
# imports every environment variable as a variable, and it propagates through
# `make package/<p>/compile`, the metadata dump and the sub-makes alike.
# byedpi deliberately keeps its own upstream version (see its Makefile).
echo "[sdk] package version: ${SHATER_PKG_VERSION:-<unset -> Makefile fallback>}-r${SHATER_PKG_RELEASE:-?}"
test -f "$REPO/openwrt/shaterd/Makefile" || {
echo "[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.
case "$ARCH" in
x86_64) sfx=amd64 ;;
aarch64_cortex-a53) sfx=arm64 ;;
*) echo "[sdk] ERROR: unsupported ARCH '$ARCH'"; exit 2 ;;
esac
test -f "$REPO/openwrt/shaterd/files/shaterd-$sfx.upx" || {
echo "[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; }
# --- register this repo's openwrt/ as a src-link feed named `shater` ---------
# src-link REQUIRES an absolute path; $REPO/openwrt is exactly a feed root (it
# contains the 4 package dirs and nothing else that looks like a package).
cp -f feeds.conf.default feeds.conf
grep -q '^src-link shater ' feeds.conf || echo "src-link shater $REPO/openwrt" >> feeds.conf
# Update metadata for ALL feeds: our `shater` feed + the SDK defaults (base,
# luci, packages, routing, telephony). We need `luci` for feeds/luci/luci.mk and
# `base`/`packages` for the runtime deps (kmod-nft-tproxy, kmod-nft-socket,
# ip-full, rpcd, luci-base) to resolve.
#
# Persistent feeds checkouts: $FEEDS_CACHE (a workspace dir the runner restores
# via actions/cache, shared into this container via --volumes-from) replaces
# the SDK's ephemeral feeds/ dir, so `feeds update` git-fetches deltas instead
# of re-cloning base+packages+luci every run (~7 min on the runner's slow
# github.com link). Correctness-safe: update always checks out feeds.conf's
# pinned revisions; if it ever fails on a cached checkout (e.g. a force-pushed
# upstream), the cache is wiped and the update retried with fresh clones.
if [ -n "${FEEDS_CACHE:-}" ] && mkdir -p "$FEEDS_CACHE" 2>/dev/null; then
rm -rf feeds
ln -s "$FEEDS_CACHE" feeds
echo "[sdk] feeds/ -> $FEEDS_CACHE (persistent cache)"
fi
echo "[sdk] feeds update -a"
if ! ./scripts/feeds update -a; then
[ -L feeds ] || { echo "[sdk] ERROR: feeds update failed"; exit 8; }
echo "[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 "[sdk] feeds install (prefer shater feed)"
./scripts/feeds install -p shater shaterd shater-core byedpi luci-app-shater
# Select our packages, then defconfig. `make package/<p>/compile` builds the
# explicit target regardless, but selecting first makes deps visible to defconfig.
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 — sourceware.org
# (elfutils) and other upstreams intermittently stall mid-transfer, and curl's
# --connect-timeout doesn't cover a stalled stream, so the SDK download hangs the
# 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 (see ci/build-feed.sh). 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 "[sdk] defconfig"
make defconfig >/dev/null
# --- compile the 4 packages --------------------------------------------------
for p in shaterd shater-core byedpi luci-app-shater; do
echo "[sdk] === build $p ==="
make "package/$p/compile" V=s -j"$(nproc)"
done
# --- collect ONLY our 4 packages' .ipk (per-arch shaterd/byedpi + _all core/luci)
# NOT `find bin -name '*.ipk'`: the openwrt/sdk image ships HUNDREDS of prebuilt
# kmod/base .ipk under bin/, which a blanket copy would pull into the feed and
# get signed under OUR key. Match each package's own `<name>_<ver>_<arch>.ipk`.
found=0
for p in shaterd shater-core byedpi luci-app-shater; do
for ipk in $(find bin -type f -name "${p}_*.ipk"); do
cp -f "$ipk" "$OUT/"; found=$((found+1))
done
done
[ "$found" -ge 4 ] || { echo "[sdk] ERROR: expected >=4 of OUR .ipk, collected $found"; echo "[sdk] (all .ipk under bin/:)"; find bin -type f -name '*.ipk' | head -20; exit 4; }
# --- assert the tag-derived version actually reached the packages -------------
# The whole point of B4 is that a WRONG-but-plausible version ships silently. The
# env -> make hand-off has several layers (docker -e, make's env import, the
# metadata dump), so verify the result instead of trusting it: every one of our
# three tag-versioned packages must be named `<name>_<ver>-r<rel>_<arch>.ipk`.
# byedpi is excluded on purpose — it keeps upstream ByeDPI's own version.
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
ls "$OUT/${p}_${want}_"*.ipk >/dev/null 2>&1 || {
echo "[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 "[sdk] collected:"; ls -1 "$OUT" | sed 's/^/ /'
exit 12; }
done
echo "[sdk] version check OK — our 3 packages are $want"
fi
chmod -R a+rwX "$OUT" 2>/dev/null || true
echo "[sdk] OK arch=$ARCH — collected $found of our .ipk:"
ls -l "$OUT"