RKNnoVPN/.forgejo/workflows/release.yml
loop-uh 1bd20fea84
All checks were successful
RKNnoVPN Linux and Android CI / policy-and-go (push) Successful in 1m25s
RKNnoVPN Linux and Android CI / android-debug (push) Successful in 3m50s
Use official upstream sources for bundled cores
2026-08-07 15:19:53 +03:00

633 lines
24 KiB
YAML

name: Full build and Forgejo release
on:
push:
tags: ["v*"]
workflow_dispatch:
permissions:
contents: write
env:
GO_VERSION: "1.22"
SINGBOX_GO_VERSION: "1.24.x"
SINGBOX_VERSION: "1.13.0"
SINGBOX_SOURCE_URL: "https://github.com/SagerNet/sing-box.git"
SINGBOX_TAGS: "with_quic,with_wireguard,with_utls,with_clash_api,badlinkname,tfogo_checklinkname0"
XRAY_VERSION: "stable"
XRAY_SOURCE_URL: "https://github.com/XTLS/Xray-core.git"
ANDROID_API: "23"
ANDROID_NDK_VERSION: "27.2.12479018"
ANDROID_HOME: "/opt/android-sdk"
ANDROID_SDK_ROOT: "/opt/android-sdk"
jobs:
# ============================================================
# Job -1: Privacy/static policy guardrails
# ============================================================
privacy-lint:
name: Privacy Lint
runs-on: android
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Check APK privacy surface
run: bash scripts/ci/check_apk_privacy.sh
- name: Check reset.lock script contract
run: bash scripts/ci/check_reset_lock_contract.sh
- name: Check runtime snapshot contract
run: python3 scripts/ci/check_runtime_snapshot_contract.py
- name: Check release manifest metadata
run: bash scripts/ci/check_release_manifest.sh
# ============================================================
# Job -0.5: Local-first runtime architecture guardrail
# ============================================================
local-first-runtime-guardrail:
name: Local-first Runtime Guardrail
runs-on: android
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Check local-first runtime boundaries
run: bash scripts/ci/check_local_first_runtime.sh
# ============================================================
# Job 0: Go unit/policy tests
# ============================================================
go-test:
name: Go Tests
runs-on: android
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Setup Go
uses: https://data.forgejo.org/actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
- name: Test runtime packages
working-directory: runtime
run: go test ./...
# ============================================================
# Job 0.5: Android debug build + unit/instrumentation tests
# ============================================================
android-test:
name: Android Guardrails & Tests
runs-on: android
needs: [privacy-lint, local-first-runtime-guardrail]
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Validate the preinstalled JDK 17
run: |
set -euo pipefail
java_major="$(java -XshowSettings:properties -version 2>&1 \
| awk -F'= ' '/java.specification.version =/{print $2}')"
test "$java_major" = "17"
java -version
javac -version
- name: Check local-first runtime boundaries
run: bash scripts/ci/check_local_first_runtime.sh
- name: Assemble debug APK
working-directory: app
run: ./gradlew :app:assembleDebug --no-daemon --stacktrace
- name: Run Android unit tests
working-directory: app
run: ./gradlew :app:testDebugUnitTest --no-daemon --stacktrace
- name: Require instrumentation tests to use a KVM build host
working-directory: app
run: |
set -euo pipefail
if find app/src/androidTest -type f \( -name '*.kt' -o -name '*.java' \) 2>/dev/null | grep -q .; then
echo 'Android instrumentation tests require the external KVM build host.' >&2
exit 1
fi
echo 'No Android instrumentation tests are present.'
# ============================================================
# Job 1: Resolve sing-box release tag
# ============================================================
resolve-singbox:
name: Resolve sing-box release
runs-on: android
outputs:
version: ${{ steps.resolve.outputs.version }}
ref: ${{ steps.resolve.outputs.ref }}
steps:
- name: Resolve sing-box release
id: resolve
run: |
set -euo pipefail
VERSION="v${SINGBOX_VERSION#v}"
git ls-remote --exit-code --tags "${SINGBOX_SOURCE_URL}" \
"refs/tags/${VERSION}" >/dev/null
echo "version=${VERSION#v}" >> "$FORGEJO_OUTPUT"
echo "ref=$VERSION" >> "$FORGEJO_OUTPUT"
echo "Resolved sing-box $VERSION"
# ============================================================
# Job 2: Build root runtime CLI for Android ABIs
# ============================================================
build-runtime:
name: Build Runtime CLI (${{ matrix.arch }})
runs-on: android
needs: [go-test]
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
goarch: arm64
goarm: ""
- arch: armv7
goarch: arm
goarm: "7"
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Setup Go
uses: https://data.forgejo.org/actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
- name: Build runtime CLI
working-directory: runtime
run: |
if [[ "${FORGEJO_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
VERSION="${FORGEJO_REF_NAME}"
CODE="$(../tools/version.sh code "$VERSION")"
else
VERSION="dev-${FORGEJO_RUN_NUMBER}-${FORGEJO_SHA::7}"
CODE="${FORGEJO_RUN_NUMBER}"
fi
mkdir -p "../out/${{ matrix.arch }}"
CGO_ENABLED=0 GOOS=linux GOARCH=${{ matrix.goarch }} GOARM=${{ matrix.goarm }} go build \
-ldflags="-s -w -X main.Version=${VERSION} -X main.VersionCode=${CODE}" \
-o "../out/${{ matrix.arch }}/rknnovpn-runtime" ./cmd/rknnovpn-runtime
- name: Upload runtime CLI artifact
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
with:
name: runtime-cli-${{ matrix.arch }}
path: out/${{ matrix.arch }}/
retention-days: 1
# ============================================================
# Job 3: Build Android sing-box binaries for Android module runtime
# ============================================================
build-singbox:
name: Build sing-box (${{ matrix.arch }})
runs-on: android
needs: [resolve-singbox]
env:
SINGBOX_RESOLVED_VERSION: ${{ needs.resolve-singbox.outputs.version }}
SINGBOX_REF: ${{ needs.resolve-singbox.outputs.ref }}
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
goarch: arm64
goarm: ""
cc: aarch64-linux-android23-clang
linker: /system/bin/linker64
- arch: armv7
goarch: arm
goarm: "7"
cc: armv7a-linux-androideabi23-clang
linker: /system/bin/linker
steps:
- name: Checkout sing-box
run: |
git clone --depth 1 --branch "${SINGBOX_REF}" \
"${SINGBOX_SOURCE_URL}" sing-box-src
- name: Setup Go for sing-box
uses: https://data.forgejo.org/actions/setup-go@v6
with:
go-version: ${{ env.SINGBOX_GO_VERSION }}
cache-dependency-path: sing-box-src/go.sum
- name: Validate the preinstalled Android NDK
run: |
set -euo pipefail
ndk_home="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}"
test -x "$ndk_home/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang"
test -x "$ndk_home/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang"
echo "ANDROID_NDK_HOME=$ndk_home" >> "$FORGEJO_ENV"
- name: Build Android sing-box
run: |
mkdir -p "out/${{ matrix.arch }}"
cd sing-box-src
if [[ -f release/LDFLAGS ]]; then
LDFLAGS="$(cat release/LDFLAGS)"
else
LDFLAGS="-X 'internal/godebug.defaultGODEBUG=multipathtcp=0' -checklinkname=0"
fi
export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{ matrix.cc }}"
CGO_ENABLED=1 GOOS=android GOARCH=${{ matrix.goarch }} GOARM=${{ matrix.goarm }} go build \
-trimpath \
-tags "${SINGBOX_TAGS}" \
-ldflags "-X 'github.com/sagernet/sing-box/constant.Version=${SINGBOX_RESOLVED_VERSION}' ${LDFLAGS} -s -w -buildid=" \
-o "../out/${{ matrix.arch }}/sing-box" ./cmd/sing-box
cd ..
chmod 755 "out/${{ matrix.arch }}/sing-box"
if ! readelf -l "out/${{ matrix.arch }}/sing-box" | grep -q "Requesting program interpreter: ${{ matrix.linker }}"; then
echo "ERROR: sing-box is not an Android PIE binary for ${{ matrix.arch }}"
readelf -l "out/${{ matrix.arch }}/sing-box"
exit 1
fi
readelf -h "out/${{ matrix.arch }}/sing-box"
sha256sum "out/${{ matrix.arch }}/sing-box"
- name: Upload sing-box artifact
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
with:
name: singbox-${{ matrix.arch }}
path: out/${{ matrix.arch }}/
retention-days: 1
# ============================================================
# Job 4: Build Android Xray-core sidecar for VLESS/XHTTP only
# ============================================================
resolve-xray:
name: Resolve Xray-core release
runs-on: android
outputs:
version: ${{ steps.resolve.outputs.version }}
ref: ${{ steps.resolve.outputs.ref }}
steps:
- name: Resolve Xray-core release
id: resolve
run: |
set -euo pipefail
case "${XRAY_VERSION}" in
stable|"")
VERSION="$(curl -fsSL --retry 3 --connect-timeout 15 \
https://api.github.com/repos/XTLS/Xray-core/releases/latest \
| jq -r .tag_name)"
;;
latest|alpha|prerelease)
VERSION="$(curl -fsSL --retry 3 --connect-timeout 15 \
'https://api.github.com/repos/XTLS/Xray-core/releases?per_page=1' \
| jq -r '.[0].tag_name')"
;;
v*)
VERSION="${XRAY_VERSION}"
;;
*)
VERSION="v${XRAY_VERSION#v}"
;;
esac
if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then
echo "Failed to resolve Xray-core release" >&2
exit 1
fi
echo "version=${VERSION#v}" >> "$FORGEJO_OUTPUT"
echo "ref=$VERSION" >> "$FORGEJO_OUTPUT"
echo "Resolved Xray-core $VERSION"
build-xray:
name: Build Xray-core (${{ matrix.arch }})
runs-on: android
needs: [resolve-xray]
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
goarch: arm64
goarm: ""
cc: aarch64-linux-android23-clang
linker: /system/bin/linker64
- arch: armv7
goarch: arm
goarm: "7"
cc: armv7a-linux-androideabi23-clang
linker: /system/bin/linker
steps:
- name: Checkout Xray-core
env:
XRAY_REF: ${{ needs.resolve-xray.outputs.ref }}
run: |
git clone --depth 1 --branch "${XRAY_REF}" \
"${XRAY_SOURCE_URL}" xray-src
- name: Setup Go for Xray-core
uses: https://data.forgejo.org/actions/setup-go@v6
with:
# Xray-core raises its minimum Go version over time. The selected
# upstream tag is the source of truth, so a new stable release cannot
# silently outgrow a stale version duplicated in this workflow.
go-version-file: xray-src/go.mod
cache-dependency-path: xray-src/go.sum
- name: Validate the preinstalled Android NDK
run: |
set -euo pipefail
ndk_home="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}"
test -x "$ndk_home/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang"
test -x "$ndk_home/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang"
echo "ANDROID_NDK_HOME=$ndk_home" >> "$FORGEJO_ENV"
- name: Build Android Xray-core
run: |
mkdir -p "out/${{ matrix.arch }}"
cd xray-src
export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{ matrix.cc }}"
CGO_ENABLED=1 GOOS=android GOARCH=${{ matrix.goarch }} GOARM=${{ matrix.goarm }} go build \
-trimpath \
-ldflags "-s -w -buildid=" \
-o "../out/${{ matrix.arch }}/xray" ./main
cd ..
chmod 755 "out/${{ matrix.arch }}/xray"
if ! readelf -l "out/${{ matrix.arch }}/xray" | grep -q "Requesting program interpreter: ${{ matrix.linker }}"; then
echo "ERROR: xray is not an Android PIE binary for ${{ matrix.arch }}"
readelf -l "out/${{ matrix.arch }}/xray"
exit 1
fi
readelf -h "out/${{ matrix.arch }}/xray"
sha256sum "out/${{ matrix.arch }}/xray"
- name: Upload Xray-core artifact
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
with:
name: xray-${{ matrix.arch }}
path: out/${{ matrix.arch }}/
retention-days: 1
# ============================================================
# Job 5: Assemble Magisk module ZIP
# ============================================================
build-module:
name: Build Magisk Module
runs-on: android
needs: [resolve-singbox, build-runtime, build-singbox, build-xray]
outputs:
singbox_version: ${{ needs.resolve-singbox.outputs.version }}
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Download runtime CLI
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: runtime-cli-arm64
path: module/binaries/arm64/
- name: Download runtime CLI (armv7)
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: runtime-cli-armv7
path: module/binaries/armv7/
- name: Download sing-box binary
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: singbox-arm64
path: module/binaries/arm64/
- name: Download sing-box binary (armv7)
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: singbox-armv7
path: module/binaries/armv7/
- name: Download Xray-core binary
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: xray-arm64
path: module/binaries/arm64/
- name: Download Xray-core binary (armv7)
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: xray-armv7
path: module/binaries/armv7/
- name: Set permissions
run: chmod 755 module/binaries/arm64/* module/binaries/armv7/*
- name: Stamp version in module.prop
run: |
if [[ "${FORGEJO_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
VERSION="${FORGEJO_REF_NAME}"
CODE="$(tools/version.sh code "$VERSION")"
else
VERSION="dev-${FORGEJO_RUN_NUMBER}-${FORGEJO_SHA::7}"
CODE="${FORGEJO_RUN_NUMBER}"
fi
sed -i "s/^version=.*/version=${VERSION}/" module/module.prop
sed -i "s/^versionCode=.*/versionCode=${CODE}/" module/module.prop
cat module/module.prop
- name: Build ZIP
run: |
cd module
zip -r ../rknnovpn-module.zip . \
-x "*.git*" "*.DS_Store"
cd ..
for dir in arm64 armv7; do
unzip -l rknnovpn-module.zip "binaries/${dir}/rknnovpn-runtime" "binaries/${dir}/sing-box" "binaries/${dir}/xray" >/dev/null
done
sha256sum rknnovpn-module.zip
- name: Upload module ZIP
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
with:
name: rknnovpn-module
path: rknnovpn-module.zip
retention-days: 5
# ============================================================
# Job 5: Build Android APK
# ============================================================
build-apk:
name: Build APK
runs-on: android
needs: [privacy-lint, android-test]
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- name: Validate the preinstalled JDK 17
run: |
set -euo pipefail
java_major="$(java -XshowSettings:properties -version 2>&1 \
| awk -F'= ' '/java.specification.version =/{print $2}')"
test "$java_major" = "17"
java -version
javac -version
- name: Stamp version
run: |
if [[ "${FORGEJO_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
VERSION="${FORGEJO_REF_NAME}"
VER_NAME="$VERSION"
VER_CODE="$(tools/version.sh code "$VERSION")"
else
VER_NAME="dev-${FORGEJO_RUN_NUMBER}-${FORGEJO_SHA::7}"
VER_CODE="${FORGEJO_RUN_NUMBER}"
fi
echo "RKNNOVPN_VERSION=${VER_NAME}" >> "$FORGEJO_ENV"
echo "RKNNOVPN_VERSION_CODE=${VER_CODE}" >> "$FORGEJO_ENV"
- name: Build and sign APK
working-directory: app
env:
RKNNOVPN_RELEASE_KEYSTORE_BASE64: ${{ secrets.RKNNOVPN_RELEASE_KEYSTORE_BASE64 }}
RKNNOVPN_RELEASE_STORE_PASSWORD: ${{ secrets.RKNNOVPN_RELEASE_STORE_PASSWORD }}
RKNNOVPN_RELEASE_KEY_ALIAS: ${{ secrets.RKNNOVPN_RELEASE_KEY_ALIAS }}
RKNNOVPN_RELEASE_KEY_PASSWORD: ${{ secrets.RKNNOVPN_RELEASE_KEY_PASSWORD }}
run: |
STORE_FILE="${RUNNER_TEMP}/rknnovpn-release.jks"
if [ -n "${RKNNOVPN_RELEASE_KEYSTORE_BASE64:-}" ]; then
printf '%s' "${RKNNOVPN_RELEASE_KEYSTORE_BASE64}" | base64 -d > "${STORE_FILE}"
elif [[ "${FORGEJO_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "ERROR: stable tag builds require RKNNOVPN_RELEASE_KEYSTORE_BASE64 signing secret" >&2
exit 1
else
RKNNOVPN_RELEASE_STORE_PASSWORD="dev-release-store-${FORGEJO_RUN_ID}"
RKNNOVPN_RELEASE_KEY_PASSWORD="${RKNNOVPN_RELEASE_STORE_PASSWORD}"
RKNNOVPN_RELEASE_KEY_ALIAS="rknnovpn-dev"
keytool -genkeypair \
-keystore "${STORE_FILE}" \
-storepass "${RKNNOVPN_RELEASE_STORE_PASSWORD}" \
-keypass "${RKNNOVPN_RELEASE_KEY_PASSWORD}" \
-alias "${RKNNOVPN_RELEASE_KEY_ALIAS}" \
-keyalg RSA \
-keysize 4096 \
-validity 30 \
-dname "CN=RKNnoVPN Dev Release,O=RKNnoVPN,C=RU"
fi
export RKNNOVPN_RELEASE_STORE_FILE="${STORE_FILE}"
export RKNNOVPN_RELEASE_STORE_PASSWORD
export RKNNOVPN_RELEASE_KEY_ALIAS
export RKNNOVPN_RELEASE_KEY_PASSWORD
./gradlew assembleRelease --no-daemon --stacktrace
APK=$(find app/build/outputs/apk/release -name "*.apk" | head -1)
if [ -z "$APK" ]; then
echo "ERROR: No APK found!"
find . -name "*.apk" -ls
exit 1
fi
echo "APK found: $APK"
cp "$APK" ../rknnovpn-panel.apk
bash ../scripts/ci/check_release_apk_privacy.sh ../rknnovpn-panel.apk
sha256sum ../rknnovpn-panel.apk
- name: Upload APK
uses: https://data.forgejo.org/forgejo/upload-artifact@v4
with:
name: rknnovpn-apk
path: rknnovpn-panel.apk
retention-days: 5
# ============================================================
# Job 6: Create Forgejo Release
# ============================================================
release:
name: Create Release
runs-on: android
if: forgejo.event_name == 'push' && startsWith(forgejo.ref, 'refs/tags/v')
needs: [build-module, build-apk]
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
with:
fetch-depth: 0
- name: Resolve release version
id: version
run: |
if [[ "${FORGEJO_REF_NAME}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
VERSION="${FORGEJO_REF_NAME}"
STABLE=true
elif [[ "${FORGEJO_REF_NAME}" == v* ]]; then
echo "ERROR: release tags must be strict vMAJOR.MINOR.PATCH, got ${FORGEJO_REF_NAME}" >&2
exit 1
else
VERSION="dev-${FORGEJO_RUN_NUMBER}-${FORGEJO_SHA::7}"
STABLE=false
fi
echo "version=${VERSION}" >> "$FORGEJO_OUTPUT"
echo "stable=${STABLE}" >> "$FORGEJO_OUTPUT"
- name: Download Magisk module
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: rknnovpn-module
path: release/
- name: Download APK
uses: https://data.forgejo.org/forgejo/download-artifact@v4
with:
name: rknnovpn-apk
path: release/
- name: Rename with version
run: |
VERSION="${{ steps.version.outputs.version }}"
mv release/rknnovpn-module.zip "release/rknnovpn-${VERSION}-module.zip"
mv release/rknnovpn-panel.apk "release/rknnovpn-${VERSION}-panel.apk"
# Generate checksums
cd release && sha256sum * > SHA256SUMS.txt && cd ..
- name: Validate update.json for Magisk manager
if: steps.version.outputs.stable == 'true'
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
VERSION_CODE="$(tools/version.sh code "$VERSION")"
test "$(jq -r .version update.json)" = "$VERSION"
test "$(jq -r .versionCode update.json)" = "$VERSION_CODE"
test "$(jq -r .zipUrl update.json)" = \
"https://git.zapret.moe/zapretdiscordyoutube/RKNnoVPN/releases/download/${VERSION}/rknnovpn-${VERSION}-module.zip"
test "$(jq -r .changelog update.json)" = \
"https://git.zapret.moe/zapretdiscordyoutube/RKNnoVPN/releases/tag/${VERSION}"
- name: Create Forgejo Release
uses: https://data.forgejo.org/actions/forgejo-release@v2.13.4
with:
direction: upload
tag: ${{ steps.version.outputs.version }}
sha: ${{ forgejo.sha }}
title: "RKNnoVPN ${{ steps.version.outputs.version }}"
release-dir: release
override: true
prerelease: ${{ steps.version.outputs.stable != 'true' }}
release-notes: |
## RKNnoVPN ${{ steps.version.outputs.version }}
### Downloads
| File | Description |
|------|-------------|
| `rknnovpn-${{ steps.version.outputs.version }}-module.zip` | Magisk/KernelSU/APatch module - flash via manager |
| `rknnovpn-${{ steps.version.outputs.version }}-panel.apk` | Controller APK - install on same device |
### Quick Start
1. Flash the module ZIP via Magisk Manager / KSU Manager / APatch
2. Reboot
3. Install the APK
4. Open RKNnoVPN Panel and add a server (paste VLESS/Trojan/SS link)
5. Select apps to proxy in the Apps tab
6. Tap Connect
### What's inside
- **No VPN, no TUN, no icon** - uses tproxy+iptables at kernel level
- **Whitelist mode** - only selected apps go through proxy
- **Full IPv4+IPv6** - mirrored iptables/ip6tables rules
- **sing-box v${{ needs.build-module.outputs.singbox_version }}** - VLESS, Trojan, VMess, SS, SOCKS, Hysteria2, TUIC
- **Xray-core sidecar** - used only for VLESS/XHTTP profiles
- **Explicit cleanup** - reset/cleanup scripts for root networking state