lx-release.yml: new build_linux_musl job (amd64/arm64/armv7/mipsle) that clones cronet-go, fetches the Chromium musl toolchain via cmd/build-naive, and builds CGO_ENABLED=1 with with_musl (swapping with_purego) so libcronet is linked statically — no libdl.so.2, runs on musl routers, naive kept. Linux moves out of the desktop build job. Artifact names mirror upstream arch suffixes (armv7, mipsle-softfloat) without the -musl suffix since Linux ships a single (musl) variant. lx-ci.yml: dispatch-only linux_musl smoke job runs the same pipeline (build + verify statically-linked / no libdl) without publishing.
344 lines
13 KiB
YAML
344 lines
13 KiB
YAML
name: lx-release
|
||
|
||
# Cross-builds the drop-in `sing-box` binary for all platforms and publishes a
|
||
# GitHub Release with archives + checksums. Triggered by pushing a tag like
|
||
# v1.13.13-lx.1, or manually via workflow_dispatch. See SPECS/004.
|
||
|
||
on:
|
||
push:
|
||
tags: ['v*-lx.*']
|
||
workflow_dispatch:
|
||
inputs:
|
||
tag:
|
||
description: 'Release tag, e.g. v1.13.13-lx.1 (created at the current ref if missing)'
|
||
required: true
|
||
|
||
permissions:
|
||
contents: write
|
||
|
||
# Build tags are owned by Makefile.lx (single source of truth). The desktop build
|
||
# uses its LX_TAGS default; the Android AAR uses build_libbox's own tag set + the
|
||
# lx: features baked into it. `make -f Makefile.lx -s lx-print-tags` prints the set
|
||
# for the release notes so nothing is duplicated here.
|
||
|
||
jobs:
|
||
build:
|
||
name: build ${{ matrix.goos }}/${{ matrix.goarch }}
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
# Linux lives in the build_linux_musl job (static musl + naive, for
|
||
# routers). See SPECS/006. This job covers the purego/native targets
|
||
# where libdl is a non-issue.
|
||
- { goos: darwin, goarch: amd64 }
|
||
- { goos: darwin, goarch: arm64 }
|
||
- { goos: windows, goarch: amd64, ext: .exe }
|
||
- { goos: windows, goarch: arm64, ext: .exe }
|
||
# Windows 7 (32-bit): built with a Win7-patched Go, and without
|
||
# with_naive_outbound (cronet-go has no windows/386 build). See SPECS/004.
|
||
- { goos: windows, goarch: "386", ext: .exe, legacy_win7: true, legacy_name: windows-7 }
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
submodules: recursive
|
||
fetch-depth: 0
|
||
|
||
- uses: actions/setup-go@v5
|
||
if: ${{ ! matrix.legacy_win7 }}
|
||
with:
|
||
go-version-file: go.mod
|
||
check-latest: true
|
||
|
||
# Win7 needs a Go toolchain that still targets Windows 7: setup_go_for_windows7.sh
|
||
# fetches stock Go and applies MetaCubeX/go patches reverting the Win7 removals.
|
||
- name: Cache Win7 Go toolchain
|
||
if: matrix.legacy_win7
|
||
id: cache-go-win7
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ~/go/go_win7
|
||
key: go_win7_${{ hashFiles('.github/setup_go_for_windows7.sh') }}
|
||
- name: Build Win7 Go toolchain
|
||
if: matrix.legacy_win7 && steps.cache-go-win7.outputs.cache-hit != 'true'
|
||
env:
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
run: bash .github/setup_go_for_windows7.sh
|
||
- name: Use Win7 Go toolchain
|
||
if: matrix.legacy_win7
|
||
run: |
|
||
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> "$GITHUB_ENV"
|
||
echo "GOROOT=$HOME/go/go_win7" >> "$GITHUB_ENV"
|
||
|
||
- name: Resolve version
|
||
id: ver
|
||
run: |
|
||
TAG="${{ github.event.inputs.tag || github.ref_name }}"
|
||
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Build
|
||
env:
|
||
GOOS: ${{ matrix.goos }}
|
||
GOARCH: ${{ matrix.goarch }}
|
||
run: |
|
||
TAGS="$(make -f Makefile.lx -s lx-print-tags)"
|
||
if [ "${{ matrix.legacy_win7 }}" = "true" ]; then
|
||
# cronet-go (with_naive_outbound) has no windows/386 build — drop it.
|
||
TAGS="${TAGS/with_naive_outbound,/}"
|
||
fi
|
||
make -f Makefile.lx lx-build \
|
||
LX_TAGS="$TAGS" \
|
||
LX_VERSION="${{ steps.ver.outputs.version }}" \
|
||
LX_OUTPUT="sing-box${{ matrix.ext }}"
|
||
|
||
- name: Package
|
||
run: |
|
||
NAME="sing-box-${{ steps.ver.outputs.version }}-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||
if [ -n "${{ matrix.legacy_name }}" ]; then
|
||
NAME="${NAME}-legacy-${{ matrix.legacy_name }}"
|
||
fi
|
||
mkdir -p "stage/$NAME" dist
|
||
cp "sing-box${{ matrix.ext }}" "stage/$NAME/"
|
||
cp LICENSE LICENSING.md README.md "stage/$NAME/" 2>/dev/null || true
|
||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||
(cd stage && zip -qr "../dist/$NAME.zip" "$NAME")
|
||
else
|
||
tar -C stage -czf "dist/$NAME.tar.gz" "$NAME"
|
||
fi
|
||
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: dist-${{ matrix.goos }}-${{ matrix.goarch }}
|
||
path: dist/*
|
||
if-no-files-found: error
|
||
|
||
# Static musl Linux builds for routers (AsusWRT Merlin, OpenWrt, Keenetic).
|
||
# The desktop `build` job ships Linux via with_purego, which pulls a dynamic
|
||
# libdl.so.2 dependency (purego's //go:cgo_import_dynamic) and won't load on
|
||
# musl. Here we mirror upstream build.yml: clone cronet-go, fetch the Chromium
|
||
# musl toolchain via its cmd/build-naive, and build CGO_ENABLED=1 with
|
||
# `with_musl` — libcronet.a is linked statically and naive is preserved.
|
||
# See SPECS/006.
|
||
build_linux_musl:
|
||
name: build linux-musl/${{ matrix.asset }}
|
||
runs-on: ubuntu-latest
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- { arch: amd64, asset: linux-amd64 }
|
||
- { arch: arm64, asset: linux-arm64 }
|
||
- { arch: arm, goarm: "7", asset: linux-armv7 }
|
||
- { arch: mipsle, gomips: softfloat, asset: linux-mipsle-softfloat }
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
submodules: recursive
|
||
fetch-depth: 0
|
||
|
||
- uses: actions/setup-go@v5
|
||
with:
|
||
go-version-file: go.mod
|
||
check-latest: true
|
||
|
||
- name: Resolve version
|
||
id: ver
|
||
run: |
|
||
TAG="${{ github.event.inputs.tag || github.ref_name }}"
|
||
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
|
||
|
||
# cronet-go carries the build-naive tool + the naiveproxy/src Chromium
|
||
# toolchain sources. Pin to the same commit go.mod depends on.
|
||
- name: Clone cronet-go
|
||
run: |
|
||
set -xeuo pipefail
|
||
CRONET_GO_VERSION="$(cat .github/CRONET_GO_VERSION)"
|
||
git init ~/cronet-go
|
||
git -C ~/cronet-go remote add origin https://github.com/sagernet/cronet-go.git
|
||
git -C ~/cronet-go fetch --depth=1 origin "$CRONET_GO_VERSION"
|
||
git -C ~/cronet-go checkout FETCH_HEAD
|
||
git -C ~/cronet-go submodule update --init --recursive --depth=1
|
||
|
||
- name: Regenerate Debian keyring
|
||
run: |
|
||
set -xeuo pipefail
|
||
rm -f ~/cronet-go/naiveproxy/src/build/linux/sysroot_scripts/keyring.gpg
|
||
cd ~/cronet-go
|
||
GPG_TTY=/dev/null ./naiveproxy/src/build/linux/sysroot_scripts/generate_keyring.sh
|
||
|
||
- name: Cache Chromium toolchain
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: |
|
||
~/cronet-go/naiveproxy/src/third_party/llvm-build/
|
||
~/cronet-go/naiveproxy/src/gn/out/
|
||
~/cronet-go/naiveproxy/src/chrome/build/pgo_profiles/
|
||
~/cronet-go/naiveproxy/src/out/sysroot-build/
|
||
key: chromium-toolchain-musl-${{ matrix.arch }}-${{ hashFiles('.github/CRONET_GO_VERSION') }}
|
||
|
||
- name: Download Chromium musl toolchain
|
||
run: |
|
||
set -xeuo pipefail
|
||
cd ~/cronet-go
|
||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl download-toolchain
|
||
|
||
- name: Set Chromium toolchain environment
|
||
run: |
|
||
set -xeuo pipefail
|
||
cd ~/cronet-go
|
||
go run ./cmd/build-naive --target=linux/${{ matrix.arch }} --libc=musl env >> "$GITHUB_ENV"
|
||
|
||
- name: Build (musl + naive, static)
|
||
env:
|
||
CGO_ENABLED: "1"
|
||
GOOS: linux
|
||
GOARCH: ${{ matrix.arch }}
|
||
GOARM: ${{ matrix.goarm }}
|
||
GOMIPS: ${{ matrix.gomips }}
|
||
run: |
|
||
set -xeuo pipefail
|
||
# LX_TAGS is the single source of truth (Makefile.lx). Swap the purego
|
||
# cronet loader for the static musl one; with_naive_outbound stays.
|
||
TAGS="$(make -f Makefile.lx -s lx-print-tags)"
|
||
TAGS="${TAGS/with_purego/with_musl}"
|
||
mkdir -p dist
|
||
go build -v -trimpath -tags "$TAGS" \
|
||
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${{ steps.ver.outputs.version }}' -checklinkname=0 -s -w -buildid=" \
|
||
-o dist/sing-box ./cmd/sing-box
|
||
|
||
- name: Verify static (no libdl)
|
||
run: |
|
||
set -xeuo pipefail
|
||
file dist/sing-box
|
||
file dist/sing-box | grep -q "statically linked"
|
||
if strings -a dist/sing-box | grep -q "libdl.so.2"; then
|
||
echo "FAIL: libdl.so.2 reference present — not a static musl build"; exit 1
|
||
fi
|
||
echo "OK: statically linked, no libdl.so.2"
|
||
|
||
- name: Package
|
||
run: |
|
||
NAME="sing-box-${{ steps.ver.outputs.version }}-${{ matrix.asset }}"
|
||
mkdir -p "stage/$NAME"
|
||
cp dist/sing-box "stage/$NAME/"
|
||
cp LICENSE LICENSING.md README.md "stage/$NAME/" 2>/dev/null || true
|
||
tar -C stage -czf "dist/$NAME.tar.gz" "$NAME"
|
||
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: dist-${{ matrix.asset }}
|
||
path: dist/*.tar.gz
|
||
if-no-files-found: error
|
||
|
||
build_android:
|
||
name: build android (libbox.aar)
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
submodules: recursive
|
||
fetch-depth: 0
|
||
|
||
- uses: actions/setup-go@v5
|
||
with:
|
||
go-version-file: go.mod
|
||
check-latest: true
|
||
|
||
- name: Setup Android NDK
|
||
id: setup-ndk
|
||
uses: nttld/setup-ndk@v1
|
||
with:
|
||
ndk-version: r28
|
||
|
||
- name: Setup OpenJDK 17
|
||
run: sudo apt-get update && sudo apt-get install -y openjdk-17-jdk-headless
|
||
|
||
- name: Resolve version
|
||
id: ver
|
||
run: |
|
||
TAG="${{ github.event.inputs.tag || github.ref_name }}"
|
||
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
|
||
# build_libbox stamps Libbox.version() from `git describe` — ensure the tag exists.
|
||
git tag "$TAG" -f
|
||
|
||
- name: Build libbox.aar (with_xhttp + with_awg baked in by build_libbox)
|
||
run: |
|
||
make lib_install
|
||
export PATH="$PATH:$(go env GOPATH)/bin"
|
||
make lib_android
|
||
env:
|
||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||
|
||
- name: Package AARs
|
||
run: |
|
||
mkdir -p dist
|
||
V="${{ steps.ver.outputs.version }}"
|
||
cp libbox.aar "dist/libbox-$V.aar"
|
||
cp libbox-legacy.aar "dist/libbox-legacy-$V.aar"
|
||
|
||
- uses: actions/upload-artifact@v4
|
||
with:
|
||
name: dist-android
|
||
path: dist/*
|
||
if-no-files-found: error
|
||
|
||
release:
|
||
name: publish release
|
||
needs: [build, build_linux_musl, build_android]
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- uses: actions/download-artifact@v4
|
||
with:
|
||
path: dist
|
||
merge-multiple: true
|
||
|
||
- name: Resolve tag
|
||
id: ver
|
||
run: |
|
||
TAG="${{ github.event.inputs.tag || github.ref_name }}"
|
||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: Checksums
|
||
run: (cd dist && sha256sum * > SHA256SUMS && cat SHA256SUMS)
|
||
|
||
- name: Release notes
|
||
run: |
|
||
cat > notes.md <<EOF
|
||
**sing-box-lx ${{ steps.ver.outputs.version }}** — a thin downstream of [sing-box](https://github.com/SagerNet/sing-box) (base **v1.13.13**) adding two client-side features.
|
||
|
||
### Features
|
||
- **AmneziaWG 2.0** (\`with_awg\`) — live-validated against a real AWG2 server (handshake + keepalive + outbound traffic). Config: a \`wireguard\` endpoint with \`jc/jmin/jmax\`, \`s1\`–\`s4\`, \`h1\`–\`h4\`, \`i1\`–\`i5\`.
|
||
- **XHTTP** transport (\`with_xhttp\`) — live-validated against a real Xray (3x-ui) server in \`packet-up\`/\`auto\` mode (handshake + DNS + HTTPS + download). Xray-compatible "splithttp"; composes with Reality. (\`stream-one\` has a known framing bug — use \`auto\`.)
|
||
|
||
### Binaries
|
||
Drop-in \`sing-box\` for **darwin / windows** × {amd64, arm64}, plus a **Windows 7 (32-bit)** legacy build (\`sing-box-${{ steps.ver.outputs.version }}-windows-386-legacy-windows-7.zip\` — built with a Win7-patched Go; without naive/cronet, which has no windows/386 target).
|
||
|
||
**Linux — static musl builds for routers** (AsusWRT Merlin, OpenWrt, Keenetic): \`linux-amd64\`, \`linux-arm64\`, \`linux-armv7\`, \`linux-mipsle-softfloat\`. These are statically linked (no \`libdl.so.2\`/glibc dependency) and **keep NaïveProxy** — they run on musl routers where the previous dynamic builds failed with \`libdl.so.2: cannot open shared object file\`. See SPECS/006.
|
||
|
||
Each archive contains the \`sing-box\` binary (\`sing-box version\` reports \`${{ steps.ver.outputs.version }}\`). Verify downloads against \`SHA256SUMS\`.
|
||
|
||
### Android
|
||
\`libbox-${{ steps.ver.outputs.version }}.aar\` (+ \`libbox-legacy-…\` for SDK 21) — gomobile build of \`experimental/libbox\` with \`with_xhttp\`+\`with_awg\` enabled, for embedding in an Android app. \`Libbox.version()\` reports the lx version.
|
||
|
||
### Build tags
|
||
Desktop binary: \`$(make -f Makefile.lx -s lx-print-tags)\`
|
||
|
||
Config reference: [docs/lx-config.md](https://github.com/Leadaxe/sing-box-lx/blob/lx/docs/lx-config.md).
|
||
EOF
|
||
|
||
- name: Publish
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
run: |
|
||
gh release create "${{ steps.ver.outputs.tag }}" dist/* \
|
||
--title "sing-box-lx ${{ steps.ver.outputs.version }}" \
|
||
--notes-file notes.md \
|
||
--target "$GITHUB_SHA"
|