diff --git a/.github/workflows/package-openwrt.yml b/.github/workflows/package-openwrt.yml index 9a8d8ba9..ee26a6ae 100644 --- a/.github/workflows/package-openwrt.yml +++ b/.github/workflows/package-openwrt.yml @@ -25,6 +25,7 @@ jobs: outputs: package_version: ${{ steps.version.outputs.package_version }} apk_version: ${{ steps.version.outputs.apk_version }} + ipk_version: ${{ steps.version.outputs.ipk_version }} release_channel: ${{ steps.channel.outputs.release_channel }} steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 @@ -41,15 +42,28 @@ jobs: # in the .apk metadata. apk_version is built directly from the same # structured inputs (tag, or commit height) — no reparse of the # flattened package_version. See packaging/openwrt-apk/apk-version.sh. + # + # ipk_version is the .ipk control Version. opkg compares versions as + # dpkg does, so it reads a tag's vX.Y.Z-rcN as revision rcN and sorts + # it above the release; vX.Y.Z~rcN sorts below it. git refuses '~' in + # a ref name, so the tag carries '-' and this maps it. The leading 'v' + # stays: every released .ipk carries it, and under opkg 0.5.4 sorts + # below v0.5.3. testing/check-package-versions.sh runs this step's text. if [[ "$GITHUB_REF" == refs/tags/* ]]; then echo "package_version=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" echo "apk_version=$(sh packaging/openwrt-apk/apk-version.sh tag "${GITHUB_REF_NAME}")" >> "$GITHUB_OUTPUT" + IPK_VERSION="${GITHUB_REF_NAME}" + if [[ "$GITHUB_REF_NAME" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-((alpha|beta|pre|rc)[0-9]*)$ ]]; then + IPK_VERSION="${BASH_REMATCH[1]}~${BASH_REMATCH[2]}" + fi + echo "ipk_version=${IPK_VERSION}" >> "$GITHUB_OUTPUT" else BRANCH=$(echo "$GITHUB_REF_NAME" | sed 's|/|-|g') HEIGHT=$(git rev-list --count HEAD) HASH=$(git rev-parse --short HEAD) echo "package_version=${BRANCH}.${HEIGHT}.${HASH}" >> "$GITHUB_OUTPUT" echo "apk_version=$(sh packaging/openwrt-apk/apk-version.sh dev "${HEIGHT}")" >> "$GITHUB_OUTPUT" + echo "ipk_version=${BRANCH}.${HEIGHT}.${HASH}" >> "$GITHUB_OUTPUT" fi - name: Determine release channel @@ -332,6 +346,7 @@ jobs: - name: Build .ipk env: PKG_VERSION: ${{ needs.determine-versioning.outputs.package_version }} + IPK_VERSION: ${{ needs.determine-versioning.outputs.ipk_version }} run: ./packaging/openwrt-ipk/build-ipk.sh --arch ${{ matrix.build_arch }} --bin-dir "$GITHUB_WORKSPACE/bins" - name: Install shellcheck (if missing) diff --git a/packaging/openwrt-ipk/build-ipk.sh b/packaging/openwrt-ipk/build-ipk.sh index cdd82eec..f960a184 100755 --- a/packaging/openwrt-ipk/build-ipk.sh +++ b/packaging/openwrt-ipk/build-ipk.sh @@ -14,7 +14,13 @@ # arm 32-bit ARM routers (Cortex-A7) # x86_64 x86 routers / VMs # -# Output: dist/fips__.ipk +# Output: dist/fips__.ipk +# +# Environment: +# PKG_VERSION label for the file name (default: git describe) +# IPK_VERSION control file Version (default: PKG_VERSION). CI passes a +# release candidate as vX.Y.Z~rcN, which opkg sorts below the +# release, while PKG_VERSION keeps the tag's vX.Y.Z-rcN. # # Prerequisites: # cargo install cargo-zigbuild @@ -86,8 +92,13 @@ DIST_DIR="$PROJECT_ROOT/dist" PKG_NAME="fips" PKG_VERSION="${PKG_VERSION:-$(cd "$PROJECT_ROOT" && git describe --tags --always --dirty 2>/dev/null || echo "0.1.0")}" +IPK_VERSION="${IPK_VERSION:-$PKG_VERSION}" -echo "==> Building $PKG_NAME $PKG_VERSION for $OPENWRT_ARCH ($RUST_TARGET)" +if [ "$IPK_VERSION" = "$PKG_VERSION" ]; then + echo "==> Building $PKG_NAME $PKG_VERSION for $OPENWRT_ARCH ($RUST_TARGET)" +else + echo "==> Building $PKG_NAME $PKG_VERSION (control Version $IPK_VERSION) for $OPENWRT_ARCH ($RUST_TARGET)" +fi # --------------------------------------------------------------------------- # 1. Obtain binaries @@ -192,7 +203,7 @@ PKG_SIZE=$(du -sk "$DATA_DIR" | cut -f1) cat > "$CONTROL_DIR/control" <&2; exit 2; } -trap 'rm -rf "$TMP"; rm -f "$PROJECT_ROOT/dist/fips_${PKG_VERSION}_"*' EXIT +trap 'rm -rf "$TMP"; rm -f "$PROJECT_ROOT/dist/fips_${PKG_VERSION}"[_~]*' EXIT harness_fail() { echo "package-test: $*" >&2 @@ -227,7 +227,9 @@ done # ── Build the .ipk ────────────────────────────────────────────────────────── echo "==> build-ipk.sh" -if ! PKG_VERSION="$PKG_VERSION" \ +# The first build is V1's "IPK_VERSION unset" case, so it must not inherit an +# IPK_VERSION from the caller's environment. +if ! env -u IPK_VERSION PKG_VERSION="$PKG_VERSION" \ bash "$PROJECT_ROOT/packaging/openwrt-ipk/build-ipk.sh" --arch x86_64 --bin-dir "$BINS" \ > "$TMP/build-ipk.log" 2>&1; then cat "$TMP/build-ipk.log" >&2 @@ -240,6 +242,44 @@ tar -xzf "$IPK" -O ./data.tar.gz | tar -tzf - > "$TMP/ipk-data" \ tar -xzf "$IPK" -O ./control.tar.gz | tar -tzf - > "$TMP/ipk-control" \ || harness_fail "cannot list control.tar.gz in $IPK" +# Print the Version field of an .ipk's control file. +ipk_version() { + tar -xzf "$1" -O ./control.tar.gz | tar -xzOf - ./control \ + | sed -n 's/^Version: //p' +} + +# ── V1 and V2. The control Version comes from IPK_VERSION ─────────────────── +# The workflow passes a candidate's opkg-sortable version (vX.Y.Z~rcN) as +# IPK_VERSION and keeps the tag's form in PKG_VERSION for the file name, since +# a GitHub release renames an asset with '~' in its name. Unset, IPK_VERSION +# falls back to PKG_VERSION, as a local build expects. +got="$(ipk_version "$IPK")" || harness_fail "cannot read the control file in $IPK" +if [[ "$got" == "$PKG_VERSION" ]]; then + ok "V1 with IPK_VERSION unset the control Version is PKG_VERSION ($got)" +else + bad "V1 with IPK_VERSION unset the control Version is '$got', want $PKG_VERSION" +fi +# The first build's package is removed so a second build that names its file +# from anything but PKG_VERSION cannot be passed by reading the stale one. +RC_VERSION="${PKG_VERSION}~rc1" +rm -f "$IPK" || harness_fail "cannot remove $IPK before the second build" +if ! PKG_VERSION="$PKG_VERSION" IPK_VERSION="$RC_VERSION" \ + bash "$PROJECT_ROOT/packaging/openwrt-ipk/build-ipk.sh" --arch x86_64 --bin-dir "$BINS" \ + > "$TMP/build-ipk-rc.log" 2>&1; then + cat "$TMP/build-ipk-rc.log" >&2 + harness_fail "build-ipk.sh failed with IPK_VERSION set" +fi +if [[ ! -f "$IPK" ]]; then + bad "V2 with IPK_VERSION set build-ipk.sh wrote no $IPK; the file name must come from PKG_VERSION" +else + got="$(ipk_version "$IPK")" || harness_fail "cannot read the control file in $IPK" + if [[ "$got" == "$RC_VERSION" ]]; then + ok "V2 with IPK_VERSION set the control Version is IPK_VERSION ($got), file named from PKG_VERSION" + else + bad "V2 with IPK_VERSION set the control Version is '$got', want $RC_VERSION" + fi +fi + # ── P1 and P2. Neither package ships the dnsmasq drop-in ──────────────────── # OpenWrt's dnsmasq builds its config from UCI and reads no directory under # /etc, so .fips forwarding comes from the UCI entry 90-fips-setup adds. The