Build Module #745
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Module | |
| permissions: | |
| contents: write | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '*/30 * * * *' | |
| jobs: | |
| check-upstream: | |
| name: Check upstream for changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_build: ${{ steps.check.outputs.should_build }} | |
| steps: | |
| - name: Get upstream latest commit SHA | |
| id: upstream | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| SHA=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \ | |
| "https://api.github.com/repos/Flowseal/zapret-discord-youtube/commits/main" \ | |
| | jq -r '.sha') | |
| echo "sha=$SHA" >> "$GITHUB_OUTPUT" | |
| echo "Upstream SHA: $SHA" | |
| - name: Restore cached upstream SHA | |
| if: github.event_name == 'schedule' | |
| id: cache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| path: upstream-sha.txt | |
| key: upstream-sha-${{ steps.upstream.outputs.sha }} | |
| restore-keys: upstream-sha- | |
| - name: Decide whether to build | |
| id: check | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| echo "workflow_dispatch: always build" | |
| elif [ "${{ steps.cache.outputs.cache-hit }}" = "true" ]; then | |
| echo "should_build=false" >> "$GITHUB_OUTPUT" | |
| echo "Upstream unchanged, skipping build" | |
| else | |
| echo "should_build=true" >> "$GITHUB_OUTPUT" | |
| echo "Upstream changed, triggering build" | |
| fi | |
| - name: Write SHA file for cache | |
| if: steps.check.outputs.should_build == 'true' | |
| run: echo "${{ steps.upstream.outputs.sha }}" > upstream-sha.txt | |
| - name: Save upstream SHA to cache | |
| if: steps.check.outputs.should_build == 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| path: upstream-sha.txt | |
| key: upstream-sha-${{ steps.upstream.outputs.sha }} | |
| build-zapret: | |
| name: zapret for Android ${{ matrix.abi }} | |
| runs-on: ubuntu-latest | |
| needs: [check-upstream] | |
| if: needs.check-upstream.outputs.should_build == 'true' | |
| strategy: | |
| matrix: | |
| include: | |
| - abi: armeabi-v7a | |
| target: armv7a-linux-androideabi | |
| output: nfqws-arm | |
| - abi: arm64-v8a | |
| target: aarch64-linux-android | |
| output: nfqws-aarch64 | |
| - abi: x86 | |
| target: i686-linux-android | |
| output: nfqws-x86 | |
| - abi: x86_64 | |
| target: x86_64-linux-android | |
| output: nfqws-x86_x64 | |
| steps: | |
| - name: Checkout zapret | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: bol-van/zapret | |
| path: zapret | |
| - name: Build nfqws | |
| env: | |
| ABI: ${{ matrix.abi }} | |
| TARGET: ${{ matrix.target }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| DEPS_DIR=$GITHUB_WORKSPACE/deps | |
| export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 | |
| export API=21 | |
| export CC="$TOOLCHAIN/bin/clang --target=$TARGET$API" | |
| export AR=$TOOLCHAIN/bin/llvm-ar | |
| export AS=$CC | |
| export LD=$TOOLCHAIN/bin/ld | |
| export RANLIB=$TOOLCHAIN/bin/llvm-ranlib | |
| export STRIP=$TOOLCHAIN/bin/llvm-strip | |
| export PKG_CONFIG_PATH=$DEPS_DIR/lib/pkgconfig | |
| curl -sSL https://www.netfilter.org/pub/libnfnetlink/libnfnetlink-1.0.2.tar.bz2 | tar -xj | |
| curl -sSL https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2 | tar -xj | |
| curl -sSL https://www.netfilter.org/pub/libnetfilter_queue/libnetfilter_queue-1.0.5.tar.bz2 | tar -xj | |
| patch -p1 -d libnetfilter_queue-1.0.5 -i "$GITHUB_WORKSPACE/zapret/.github/workflows/libnetfilter_queue-android.patch" | |
| for i in libmnl libnfnetlink libnetfilter_queue; do | |
| ( | |
| cd $i-* | |
| CFLAGS="-Os -flto=auto -Wno-implicit-function-declaration" ./configure --prefix= --host=$TARGET --enable-static --disable-shared --disable-dependency-tracking | |
| make install -j$(nproc) DESTDIR=$DEPS_DIR | |
| ) | |
| sed -i "s|^prefix=.*|prefix=$DEPS_DIR|g" $DEPS_DIR/lib/pkgconfig/$i.pc | |
| done | |
| CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -I$DEPS_DIR/include" LDFLAGS="-L$DEPS_DIR/lib" make -C zapret android -j$(nproc) | |
| ELFCLEANER_TAG="$(gh api repos/termux/termux-elf-cleaner/releases/latest --jq '.tag_name')" | |
| curl -fsSL -o elf-cleaner "https://github.com/termux/termux-elf-cleaner/releases/download/${ELFCLEANER_TAG}/termux-elf-cleaner" | |
| chmod +x elf-cleaner | |
| ./elf-cleaner --api-level 21 zapret/binaries/my/* | |
| mv zapret/binaries/my/nfqws "$PWD/${{ matrix.output }}" | |
| - name: Upload nfqws artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.output }} | |
| path: ${{ matrix.output }} | |
| if-no-files-found: error | |
| build-zapret2: | |
| name: zapret2 (nfqws2) for Android ${{ matrix.abi }} | |
| runs-on: ubuntu-latest | |
| needs: [check-upstream] | |
| if: needs.check-upstream.outputs.should_build == 'true' | |
| strategy: | |
| matrix: | |
| include: | |
| - abi: armeabi-v7a | |
| target: armv7a-linux-androideabi | |
| output: nfqws2-arm | |
| - abi: arm64-v8a | |
| target: aarch64-linux-android | |
| output: nfqws2-aarch64 | |
| - abi: x86 | |
| target: i686-linux-android | |
| output: nfqws2-x86 | |
| - abi: x86_64 | |
| target: x86_64-linux-android | |
| output: nfqws2-x86_x64 | |
| steps: | |
| - name: Checkout zapret2 | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: bol-van/zapret2 | |
| path: zapret2 | |
| # Ship the Lua DPI library that nfqws2 needs at runtime (--lua-init). Uploaded | |
| # once (from a single matrix leg) so it matches the exact nfqws2 build commit. | |
| - name: Upload zapret2 Lua scripts | |
| if: matrix.abi == 'arm64-v8a' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: zapret2-lua | |
| path: zapret2/lua/*.lua | |
| if-no-files-found: error | |
| - name: Set up build tools (gcc-multilib for the LuaJIT host buildvm) | |
| run: | | |
| sudo dpkg --add-architecture i386 | |
| sudo apt-get update -qq | |
| sudo apt-get install -y gcc-multilib | |
| - name: Build nfqws2 | |
| env: | |
| ABI: ${{ matrix.abi }} | |
| API: "21" | |
| TARGET: ${{ matrix.target }} | |
| GH_TOKEN: ${{ github.token }} | |
| LUAJIT_VER: "2.1" | |
| LUAJIT_RELEASE: "2.1-20250826" | |
| LUAJIT_LUAVER: "5.1" | |
| MINSIZE: "-Oz -flto=auto -ffunction-sections -fdata-sections" | |
| LDMINSIZE: "-Wl,--gc-sections -flto=auto" | |
| run: | | |
| DEPS_DIR=$GITHUB_WORKSPACE/deps | |
| export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 | |
| export CC="$TOOLCHAIN/bin/clang --target=$TARGET$API" | |
| export AR=$TOOLCHAIN/bin/llvm-ar | |
| export AS=$CC | |
| export LD=$TOOLCHAIN/bin/ld | |
| export RANLIB=$TOOLCHAIN/bin/llvm-ranlib | |
| export STRIP=$TOOLCHAIN/bin/llvm-strip | |
| export PKG_CONFIG_PATH=$DEPS_DIR/lib/pkgconfig | |
| SYSMALLOC=-DLUAJIT_USE_SYSMALLOC | |
| CPU="" | |
| PAGESIZE="" | |
| case "$ABI" in | |
| armeabi-v7a) CPU="-mthumb" ;; | |
| arm64-v8a) SYSMALLOC=; PAGESIZE="-Wl,-z,max-page-size=16384" ;; | |
| esac | |
| # LuaJIT (nfqws2 links it statically). buildvm runs on the host, hence -m32 for 32-bit targets. | |
| curl -sSL "https://github.com/openresty/luajit2/archive/refs/tags/v${LUAJIT_RELEASE}.tar.gz" | tar -xz | |
| case "$ABI" in | |
| *64*) HOSTCC="cc" ;; | |
| *) HOSTCC="cc -m32" ;; | |
| esac | |
| ( | |
| cd luajit2-* | |
| make BUILDMODE=static XCFLAGS="$SYSMALLOC -DLUAJIT_DISABLE_FFI" HOST_CC="$HOSTCC" CROSS= \ | |
| CC="$CC" TARGET_AR="$AR rcus" TARGET_STRIP="$STRIP" \ | |
| TARGET_CFLAGS="$CPU $MINSIZE" TARGET_LDFLAGS="$LDMINSIZE" -j"$(nproc)" | |
| make install PREFIX= DESTDIR="$DEPS_DIR" | |
| ) | |
| LCFLAGS="-I${DEPS_DIR}/include/luajit-${LUAJIT_VER}" | |
| LLIB="-L${DEPS_DIR}/lib -lluajit-${LUAJIT_LUAVER}" | |
| # netfilter libs (same patched deps as zapret) | |
| curl -sSL https://www.netfilter.org/pub/libnfnetlink/libnfnetlink-1.0.2.tar.bz2 | tar -xj | |
| curl -sSL https://www.netfilter.org/pub/libmnl/libmnl-1.0.5.tar.bz2 | tar -xj | |
| curl -sSL https://www.netfilter.org/pub/libnetfilter_queue/libnetfilter_queue-1.0.5.tar.bz2 | tar -xj | |
| patch -p1 -d libnetfilter_queue-1.0.5 -i "$GITHUB_WORKSPACE/zapret2/.github/workflows/libnetfilter_queue-android.patch" | |
| for i in libmnl libnfnetlink libnetfilter_queue; do | |
| ( | |
| cd $i-* | |
| CFLAGS="$CPU $MINSIZE -Wno-implicit-function-declaration" LDFLAGS="$LDMINSIZE" \ | |
| ./configure --prefix= --host="$TARGET" --enable-static --disable-shared --disable-dependency-tracking | |
| make install -j"$(nproc)" DESTDIR="$DEPS_DIR" | |
| ) | |
| sed -i "s|^prefix=.*|prefix=$DEPS_DIR|g" "$DEPS_DIR/lib/pkgconfig/$i.pc" | |
| done | |
| CFLAGS="-DZAPRET_GH_VER=${{ github.ref_name }} -DZAPRET_GH_HASH=${{ github.sha }} -I$DEPS_DIR/include $CPU" \ | |
| LDFLAGS="-L$DEPS_DIR/lib $PAGESIZE" \ | |
| make -C zapret2 LUA_JIT=1 LUA_CFLAGS="$LCFLAGS" LUA_LIB="$LLIB" -j"$(nproc)" android | |
| ELFCLEANER_TAG="$(gh api repos/termux/termux-elf-cleaner/releases/latest --jq '.tag_name')" | |
| curl -fsSL -o elf-cleaner "https://github.com/termux/termux-elf-cleaner/releases/download/${ELFCLEANER_TAG}/termux-elf-cleaner" | |
| chmod +x elf-cleaner | |
| ./elf-cleaner --api-level "$API" zapret2/binaries/my/* | |
| mv zapret2/binaries/my/nfqws2 "$PWD/${{ matrix.output }}" | |
| - name: Upload nfqws2 artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.output }} | |
| path: ${{ matrix.output }} | |
| if-no-files-found: error | |
| build-dnscrypt: | |
| name: dnscrypt-proxy for Android | |
| runs-on: ubuntu-latest | |
| needs: [check-upstream] | |
| if: needs.check-upstream.outputs.should_build == 'true' | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Checkout dnscrypt-proxy | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: DNSCrypt/dnscrypt-proxy | |
| path: dnscrypt-proxy | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: "1" | |
| check-latest: true | |
| cache-dependency-path: "dnscrypt-proxy/**/go.sum" | |
| - name: Build dnscrypt-proxy binaries | |
| run: | | |
| SRC_DIR="${GITHUB_WORKSPACE}/dnscrypt-proxy" | |
| if [ -d "${SRC_DIR}/dnscrypt-proxy" ]; then | |
| SRC_DIR="${SRC_DIR}/dnscrypt-proxy" | |
| fi | |
| OUTPUT_DIR="${GITHUB_WORKSPACE}/dnscrypt-proxy/binaries" | |
| mkdir -p "${OUTPUT_DIR}" | |
| cd "${SRC_DIR}" | |
| bash "${GITHUB_WORKSPACE}/.github/scripts/modified-ci-build.sh" | |
| mv dnscrypt-proxy-* "${OUTPUT_DIR}/" | |
| - name: Upload dnscrypt-proxy artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dnscrypt-proxy | |
| path: dnscrypt-proxy/binaries/* | |
| if-no-files-found: error | |
| build-curl: | |
| name: curl from Termux packages | |
| runs-on: ubuntu-latest | |
| needs: [check-upstream] | |
| if: needs.check-upstream.outputs.should_build == 'true' | |
| steps: | |
| - name: Download and extract curl binaries from Termux .deb packages | |
| run: | | |
| set -e | |
| BASE_URL="https://packages.termux.dev/apt/termux-main/pool/main/c/curl" | |
| mkdir -p curl-debs curl-work binaries | |
| curl -fsSL "$BASE_URL/" -o curl-index.html | |
| # Pick exactly one curl .deb per ABI — the highest version available. | |
| # The Termux pool can legitimately list several versions per arch, so a | |
| # blanket "must be exactly 4" check would fail on a normal upstream state. | |
| debs=() | |
| for abi in aarch64 arm i686 x86_64; do | |
| deb="$(grep -oE "curl_[^\"<>[:space:]]+_${abi}\.deb" curl-index.html | sort -V | tail -n1)" | |
| [ -n "$deb" ] || { echo "! No curl .deb found for ABI: $abi"; exit 1; } | |
| debs+=("$deb") | |
| done | |
| printf 'Selected curl .deb: %s\n' "${debs[@]}" | |
| for deb in "${debs[@]}"; do | |
| echo "Downloading $deb" | |
| curl -fsSL "$BASE_URL/$deb" -o "curl-debs/$deb" | |
| pkg_dir="curl-work/${deb%.deb}" | |
| root_dir="$pkg_dir/root" | |
| mkdir -p "$pkg_dir" "$root_dir" | |
| ( | |
| cd "$pkg_dir" | |
| ar x "../../curl-debs/$deb" | |
| ) | |
| data_archive="$(find "$pkg_dir" -maxdepth 1 -type f \( -name 'data.tar.xz' -o -name 'data.tar.gz' -o -name 'data.tar.zst' -o -name 'data.tar' \) | head -n1)" | |
| [ -n "$data_archive" ] || { echo "! data archive not found in $deb"; exit 1; } | |
| case "$data_archive" in | |
| *.tar.xz) tar -xJf "$data_archive" -C "$root_dir" ;; | |
| *.tar.gz) tar -xzf "$data_archive" -C "$root_dir" ;; | |
| *.tar.zst) tar --zstd -xf "$data_archive" -C "$root_dir" ;; | |
| *.tar) tar -xf "$data_archive" -C "$root_dir" ;; | |
| *) echo "! Unsupported archive format: $data_archive"; exit 1 ;; | |
| esac | |
| bin_path="$(find "$root_dir" -type f -path '*/data/data/com.termux/files/usr/bin/curl' | head -n1)" | |
| [ -n "$bin_path" ] || { echo "! curl binary not found inside $deb"; exit 1; } | |
| case "$deb" in | |
| *_aarch64.deb) out_name="curl-aarch64" ;; | |
| *_arm.deb) out_name="curl-arm" ;; | |
| *_i686.deb) out_name="curl-x86" ;; | |
| *_x86_64.deb) out_name="curl-x86_64" ;; | |
| *) echo "! Unknown ABI for $deb"; exit 1 ;; | |
| esac | |
| install -m 0755 "$bin_path" "binaries/$out_name" | |
| file "binaries/$out_name" || true | |
| done | |
| - name: Upload curl artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: curl-binaries | |
| path: binaries/* | |
| if-no-files-found: error | |
| validate-and-package: | |
| name: Validate and package module | |
| runs-on: ubuntu-latest | |
| needs: [check-upstream, build-zapret, build-zapret2, build-dnscrypt, build-curl] | |
| if: needs.check-upstream.outputs.should_build == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ github.ref_name }} | |
| - name: Determine version info | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| # Generate timestamp in Moscow time (UTC+3) | |
| BUILD_TIME=$(TZ='Europe/Moscow' date '+%d-%m-%y %H:%M:%S') | |
| RELEASE_COUNT="$(gh api "repos/${REPO}/releases" --paginate --jq 'length' | awk '{s+=$1} END {print s+0}')" | |
| VERSION_CODE=$((RELEASE_COUNT + 1)) | |
| TAG="${VERSION_CODE}" | |
| VERSION="v${TAG}" | |
| RELEASE_NAME="v${VERSION_CODE}" | |
| # Fetch repo description from GitHub API | |
| DESCRIPTION="$(gh api "repos/${REPO}" --jq '.description // "Zapret module"')" | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "version_code=${VERSION_CODE}" >> "$GITHUB_OUTPUT" | |
| echo "description=${DESCRIPTION}" >> "$GITHUB_OUTPUT" | |
| echo "release_name=${RELEASE_NAME}" >> "$GITHUB_OUTPUT" | |
| echo "tag=${TAG}" >> "$GITHUB_OUTPUT" | |
| echo "Version : ${VERSION}" | |
| echo "VersionCode : ${VERSION_CODE}" | |
| echo "ReleaseCount : ${RELEASE_COUNT}" | |
| echo "Release Name : ${RELEASE_NAME}" | |
| echo "Tag : ${TAG}" | |
| echo "Description : ${DESCRIPTION}" | |
| - name: Patch module.prop | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| VERSION_CODE="${{ steps.version.outputs.version_code }}" | |
| DESCRIPTION="${{ steps.version.outputs.description }}" | |
| # Escape characters that are special in a sed replacement (\, &, and the | delimiter) | |
| DESCRIPTION_ESCAPED="$(printf '%s' "$DESCRIPTION" | sed -e 's/[\\&|]/\\&/g')" | |
| sed -i "s|^version=.*|version=${VERSION}|" module/module.prop | |
| sed -i "s|^versionCode=.*|versionCode=${VERSION_CODE}|" module/module.prop | |
| sed -i "s|^description=.*|description=${DESCRIPTION_ESCAPED}|" module/module.prop | |
| echo "=== module.prop ===" | |
| cat module/module.prop | |
| - name: Patch update.json | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| VERSION_CODE="${{ steps.version.outputs.version_code }}" | |
| TAG="${{ steps.version.outputs.tag }}" | |
| REPO="${{ github.repository }}" | |
| cat > update.json <<EOF | |
| { | |
| "version": "${VERSION}", | |
| "versionCode": ${VERSION_CODE}, | |
| "zipUrl": "https://github.com/${REPO}/releases/download/${TAG}/zapret-pocket.zip", | |
| "changelog": "https://raw.githubusercontent.com/${REPO}/main/CHANGELOG.md" | |
| } | |
| EOF | |
| echo "=== update.json ===" | |
| cat update.json | |
| - name: Generate CHANGELOG.md | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| python3 - <<'PYEOF' | |
| import subprocess | |
| import os | |
| import textwrap | |
| repo = os.environ["REPO"] | |
| tags_raw = subprocess.check_output( | |
| ["git", "tag", "--list", "--sort=-creatordate"], | |
| text=True, | |
| ).splitlines() | |
| prev_tag = next((tag for tag in tags_raw if tag.isdigit()), "") | |
| log_cmd = ["git", "log", "--format=%h %s"] | |
| if prev_tag: | |
| log_cmd = ["git", "log", f"{prev_tag}..HEAD", "--format=%h %s"] | |
| commits_raw = subprocess.check_output(log_cmd, text=True).splitlines() | |
| if not commits_raw: | |
| commits_raw = [subprocess.check_output(["git", "log", "-1", "--format=%h %s"], text=True).strip()] | |
| lines = [] | |
| for entry in commits_raw: | |
| parts = entry.split(" ", 1) | |
| short_sha = parts[0] | |
| msg = parts[1] if len(parts) > 1 else short_sha | |
| lines.append(f"- {msg} {short_sha}") | |
| body = "\n".join(lines) | |
| # Static links header | |
| header = textwrap.dedent("""\ | |
| [Telegram Channel](https://t.me/sevcator/921) | |
| [Repository](https://github.com/sevcator/zapret-pocket/) | |
| [Report Issues](https://github.com/sevcator/zapret-pocket/issues) | |
| [Donate](https://t.me/sevcator/909) | |
| [Author](https://github.com/sevcator/) | |
| """) | |
| content = header + "\n" + body + "\n" | |
| with open("CHANGELOG.md", "w", encoding="utf-8") as f: | |
| f.write(content) | |
| print("=== CHANGELOG.md ===") | |
| print(content) | |
| PYEOF | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Download upstream archive | |
| run: | | |
| curl -L -o flowseal-upstream.zip https://github.com/Flowseal/zapret-discord-youtube/archive/refs/heads/main.zip | |
| - name: Sync module assets from upstream zip | |
| run: | | |
| python .github/scripts/generate-assets.py | |
| - name: Download default ipset-service.txt into module cache | |
| run: | | |
| mkdir -p module/.service | |
| curl -fsSL \ | |
| -o module/.service/ipset-service.txt \ | |
| https://raw.githubusercontent.com/sevcator/zapret-lists/refs/heads/main/ipset-service.txt | |
| - name: Validate shell syntax | |
| run: | | |
| find module -type f \( -name "*.sh" -o -path "module/system/bin/zapret" \) -print0 | xargs -0 -n1 sh -n | |
| - name: Download nfqws armeabi-v7a | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws-arm | |
| path: artifacts/nfqws-arm | |
| - name: Download nfqws arm64-v8a | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws-aarch64 | |
| path: artifacts/nfqws-aarch64 | |
| - name: Download nfqws x86 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws-x86 | |
| path: artifacts/nfqws-x86 | |
| - name: Download nfqws x86_64 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws-x86_x64 | |
| path: artifacts/nfqws-x86_x64 | |
| - name: Download nfqws2 armeabi-v7a | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws2-arm | |
| path: artifacts/nfqws2-arm | |
| - name: Download nfqws2 arm64-v8a | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws2-aarch64 | |
| path: artifacts/nfqws2-aarch64 | |
| - name: Download nfqws2 x86 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws2-x86 | |
| path: artifacts/nfqws2-x86 | |
| - name: Download nfqws2 x86_64 | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: nfqws2-x86_x64 | |
| path: artifacts/nfqws2-x86_x64 | |
| - name: Download dnscrypt-proxy | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dnscrypt-proxy | |
| path: artifacts/dnscrypt-proxy | |
| - name: Download curl binaries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: curl-binaries | |
| path: artifacts/curl | |
| - name: Download zapret2 Lua scripts into module/lua | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: zapret2-lua | |
| path: module/lua | |
| - name: Validate Lua scripts present | |
| run: | | |
| ls -l module/lua | |
| # nfqws2 strategies load these two at runtime via --lua-init | |
| test -s module/lua/zapret-lib.lua | |
| test -s module/lua/zapret-antidpi.lua | |
| - name: Download latest VpnHotspot APK | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| python - <<'PY' | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from pathlib import Path | |
| from urllib.request import Request, urlopen | |
| api_url = "https://api.github.com/repos/Mygod/VPNHotspot/releases" | |
| headers = { | |
| "Accept": "application/vnd.github+json", | |
| "Authorization": f"Bearer {os.environ['GH_TOKEN']}", | |
| "X-GitHub-Api-Version": "2022-11-28", | |
| } | |
| request = Request(api_url, headers=headers) | |
| with urlopen(request) as response: | |
| releases = json.load(response) | |
| asset = None | |
| for release in releases: | |
| if release.get("draft") or release.get("prerelease"): | |
| continue | |
| for candidate in release.get("assets", []): | |
| name = candidate.get("name", "") | |
| if name.startswith("vpnhotspot-v") and name.endswith(".apk"): | |
| asset = candidate | |
| break | |
| if asset is not None: | |
| break | |
| if asset is None: | |
| raise SystemExit("Stable VpnHotspot APK asset was not found") | |
| output_dir = Path("artifacts") / "vpnhotspot" | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| output_path = output_dir / "VpnHotspot.apk" | |
| download_request = Request(asset["browser_download_url"], headers=headers) | |
| with urlopen(download_request) as response: | |
| output_path.write_bytes(response.read()) | |
| print(f"Downloaded {asset['name']} from {asset['browser_download_url']}") | |
| PY | |
| - name: Add binaries to module layout | |
| run: | | |
| mkdir -p module/zapret module/dnscrypt module/system/app | |
| mv artifacts/nfqws-arm/nfqws-arm module/zapret/nfqws-arm | |
| mv artifacts/nfqws-aarch64/nfqws-aarch64 module/zapret/nfqws-aarch64 | |
| mv artifacts/nfqws-x86/nfqws-x86 module/zapret/nfqws-x86 | |
| mv artifacts/nfqws-x86_x64/nfqws-x86_x64 module/zapret/nfqws-x86_x64 | |
| mv artifacts/nfqws2-arm/nfqws2-arm module/zapret/nfqws2-arm | |
| mv artifacts/nfqws2-aarch64/nfqws2-aarch64 module/zapret/nfqws2-aarch64 | |
| mv artifacts/nfqws2-x86/nfqws2-x86 module/zapret/nfqws2-x86 | |
| mv artifacts/nfqws2-x86_x64/nfqws2-x86_x64 module/zapret/nfqws2-x86_x64 | |
| mv artifacts/dnscrypt-proxy/* module/dnscrypt/ | |
| mv artifacts/curl/* module/ | |
| mv artifacts/vpnhotspot/VpnHotspot.apk module/system/app/VpnHotspot.apk | |
| chmod +x module/zapret/*.sh module/zapret/nfqws-* module/zapret/nfqws2-* module/dnscrypt/dnscrypt-proxy-* module/dnscrypt/*.sh module/curl-* 2>/dev/null || true | |
| - name: Package module zip | |
| run: | | |
| cd module | |
| find . -type d -empty -delete | |
| zip -r ../zapret-pocket.zip . | |
| - name: Upload module zip artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: zapret-pocket | |
| path: zapret-pocket.zip | |
| if-no-files-found: error | |
| - name: Commit updated files back to repo | |
| run: | | |
| git config user.name "sevcator" | |
| git config user.email "68725282+sevcator@users.noreply.github.com" | |
| # module/lua holds the zapret2 Lua DPI library fetched from bol-van/zapret2 | |
| # this run; commit it so the repo tracks the current upstream copy too. | |
| git add module/module.prop update.json CHANGELOG.md module/lua | |
| if git diff --cached --quiet; then | |
| echo "Nothing to commit." | |
| else | |
| git commit -m "chore: update version, changelog, update.json, lua [skip ci]" | |
| git push origin HEAD:${{ github.ref_name }} || git push origin HEAD:main | |
| fi | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| RELEASE_NAME: ${{ steps.version.outputs.release_name }} | |
| run: | | |
| TAG="${{ steps.version.outputs.tag }}" | |
| # Generate release notes: simple format | |
| python3 - <<'PYEOF' | |
| import subprocess | |
| import os | |
| tags_raw = subprocess.check_output( | |
| ["git", "tag", "--list", "--sort=-creatordate"], | |
| text=True, | |
| ).splitlines() | |
| prev_tag = next((tag for tag in tags_raw if tag.isdigit()), "") | |
| log_cmd = ["git", "log", "--format=%h %s"] | |
| if prev_tag: | |
| log_cmd = ["git", "log", f"{prev_tag}..HEAD", "--format=%h %s"] | |
| commits_raw = subprocess.check_output(log_cmd, text=True).splitlines() | |
| if not commits_raw: | |
| commits_raw = [subprocess.check_output(["git", "log", "-1", "--format=%h %s"], text=True).strip()] | |
| lines = [] | |
| for entry in commits_raw: | |
| parts = entry.split(" ", 1) | |
| short_sha = parts[0] | |
| msg = parts[1] if len(parts) > 1 else short_sha | |
| lines.append(f"- {msg} {short_sha}") | |
| text = "\n".join(lines) + "\n" | |
| with open("/tmp/release_notes.md", "w", encoding="utf-8") as out: | |
| out.write(text) | |
| print("=== Release Notes ===") | |
| print(text) | |
| PYEOF | |
| # Create tag | |
| git tag -f "${TAG}" | |
| git push origin -f "refs/tags/${TAG}" | |
| # Delete existing release with this tag if present | |
| gh release delete "${TAG}" --repo "${REPO}" --yes 2>/dev/null || true | |
| # Create release | |
| gh release create "${TAG}" --repo "${REPO}" --title "${RELEASE_NAME}" --notes-file /tmp/release_notes.md zapret-pocket.zip |