mirror of
https://github.com/okhsunrog/vpnhide.git
synced 2026-05-21 18:33:19 +00:00
fix(build): port build scripts to Python to allow Windows contributors to build subprojects (#83)
* Rewrite build-version and all build-zip bash scripts to python * Add executable permissions to python build scripts * Use python build script for kmod in CI * Fix * Enhance kmod build script, add/fix docs, CI edits * Delete remaining build-zip bash scripts * Delete remaining build-zip bash scripts
This commit is contained in:
parent
4ad2ba8c2d
commit
cf4e72fa01
20 changed files with 490 additions and 206 deletions
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
|
|
@ -77,38 +77,20 @@ jobs:
|
|||
container:
|
||||
image: ghcr.io/ylarod/ddk-min:${{ matrix.kmi }}-20260313
|
||||
env:
|
||||
KDIR: /opt/ddk/kdir/${{ matrix.kmi }}
|
||||
KMI: ${{ matrix.kmi }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build kernel module
|
||||
run: |
|
||||
CLANG=$(echo /opt/ddk/clang/clang-r*/bin)
|
||||
make -C $KDIR M=$GITHUB_WORKSPACE/kmod \
|
||||
ARCH=arm64 LLVM=1 LLVM_IAS=1 \
|
||||
CC=$CLANG/clang LD=$CLANG/ld.lld \
|
||||
AR=$CLANG/llvm-ar NM=$CLANG/llvm-nm \
|
||||
OBJCOPY=$CLANG/llvm-objcopy \
|
||||
OBJDUMP=$CLANG/llvm-objdump \
|
||||
STRIP=$CLANG/llvm-strip \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- \
|
||||
modules
|
||||
$CLANG/llvm-strip -d kmod/vpnhide_kmod.ko
|
||||
- name: Mark workspace safe
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Package KSU module zip
|
||||
- name: Build and package kernel module
|
||||
run: |
|
||||
apt-get update -qq && apt-get install -y -qq zip git >/dev/null
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
BUILD_VERSION=$(./scripts/build-version.sh)
|
||||
echo "Stamping kmod module.prop version=v${BUILD_VERSION} gkiVariant=${{ matrix.kmi }}"
|
||||
cp kmod/vpnhide_kmod.ko kmod/module/
|
||||
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" kmod/module/module.prop
|
||||
echo "gkiVariant=${{ matrix.kmi }}" >> kmod/module/module.prop
|
||||
echo "updateJson=https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-kmod-${{ matrix.kmi }}.json" >> kmod/module/module.prop
|
||||
(cd kmod/module && zip -qr "$GITHUB_WORKSPACE/vpnhide-kmod-${{ matrix.kmi }}.zip" .)
|
||||
cd kmod
|
||||
python3 ./build-zip.py --kmi $KMI
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
|
@ -150,7 +132,7 @@ jobs:
|
|||
UPDATE_JSON_URL: https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-zygisk.json
|
||||
run: |
|
||||
cd zygisk
|
||||
./build-zip.sh
|
||||
python3 ./build-zip.py
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
|
@ -225,9 +207,8 @@ jobs:
|
|||
env:
|
||||
UPDATE_JSON_URL: https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-ports.json
|
||||
run: |
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq zip >/dev/null
|
||||
cd portshide
|
||||
./build-zip.sh
|
||||
python3 ./build-zip.py
|
||||
mv vpnhide-ports.zip "$GITHUB_WORKSPACE/vpnhide-ports.zip"
|
||||
|
||||
- name: Upload artifact
|
||||
|
|
|
|||
26
README.en.md
26
README.en.md
|
|
@ -194,10 +194,32 @@ Rows 1-6, 21, and 24 are the only vectors reachable by regular apps. Everything
|
|||
|
||||
## Building from source
|
||||
|
||||
- **kmod**: `cd kmod && make && ./build-zip.sh` — see [kmod/BUILDING.md](kmod/BUILDING.md)
|
||||
- **zygisk**: `cd zygisk && ./build-zip.sh` (Rust + NDK + cargo-ndk)
|
||||
- **kmod**: `cd kmod && make && ./build-zip.py` — see [kmod/BUILDING.md](kmod/BUILDING.md)
|
||||
- **zygisk**: `cd zygisk && ./build-zip.py` (Rust + NDK + cargo-ndk)
|
||||
- **lsposed**: `cd lsposed && ./gradlew assembleDebug` (JDK 17 + Rust + NDK + cargo-ndk)
|
||||
|
||||
### Notes for contributors stuck on Windows
|
||||
|
||||
If you're on Windows, there are some inconveniences with building some subprojects.
|
||||
|
||||
**lsposed**: builds fine in Android Studio.
|
||||
|
||||
**portshide**: `cd .\portshide\; python .\build-zip.py` runs fine.
|
||||
|
||||
For the next two, you'll (unfortunately) need to install [Docker for Windows](https://docs.docker.com/desktop/setup/install/windows-install/).
|
||||
|
||||
**kmod**:
|
||||
```powershell
|
||||
$env:KMI="android12-5.10"; docker run --rm -it -v "${PWD}:/workspace" -e KMI=$env:KMI -w /workspace "ghcr.io/ylarod/ddk-min:$($env:KMI)-20260313" bash -c 'cd kmod && python3 ./build-zip.py --kmi $KMI'
|
||||
```
|
||||
Be sure to use the same version of `ylarod/ddk-min` image (the date after KMI name) as used in the `ci.yml` workflow file.
|
||||
|
||||
**zygisk**:
|
||||
```powershell
|
||||
docker run --rm -it -v "${PWD}:/workspace" -v "vpnhide_cargo_cache:/usr/local/cargo/registry" -w /workspace ghcr.io/okhsunrog/vpnhide/ci:latest bash -c 'cd zygisk && python3 ./build-zip.py'
|
||||
```
|
||||
The reason why `zygisk` can't be built directly is because source code of dependency `zygisk-api` contains a file named `aux.rs`. Cargo uses `libgit2` for git operations and it contains a guard, which forbids creating files _containing_ reserved Windows words. You'll get an error: `cannot checkout to invalid path 'src/aux.rs'; class=Checkout (20)`. [Someone reports](https://superuser.com/a/1929659), that it bacame possible to create files containing reserved words **with** an extension after some update, but it seems such behavior wasn't modified in `libgit2`.
|
||||
|
||||
## Verified against
|
||||
|
||||
- [RKNHardering](https://github.com/xtclovver/RKNHardering/) — all detection vectors clean
|
||||
|
|
|
|||
26
README.md
26
README.md
|
|
@ -194,10 +194,32 @@ vpnhide — это не один переключатель, а три разн
|
|||
|
||||
## Сборка из исходников
|
||||
|
||||
- **kmod**: `cd kmod && make && ./build-zip.sh` — см. [kmod/BUILDING.md](kmod/BUILDING.md)
|
||||
- **zygisk**: `cd zygisk && ./build-zip.sh` (Rust + NDK + cargo-ndk)
|
||||
- **kmod**: `cd kmod && make && ./build-zip.py` — см. [kmod/BUILDING.md](kmod/BUILDING.md)
|
||||
- **zygisk**: `cd zygisk && ./build-zip.py` (Rust + NDK + cargo-ndk)
|
||||
- **lsposed**: `cd lsposed && ./gradlew assembleDebug` (JDK 17 + Rust + NDK + cargo-ndk)
|
||||
|
||||
### Заметки для контрибьюторов, застрявших на Windows
|
||||
|
||||
Если вы используете Windows, при сборке некоторых подпроектов возникают определенные неудобства.
|
||||
|
||||
**lsposed**: отлично собирается в Android Studio.
|
||||
|
||||
**portshide**: `cd .\portshide\; python .\build-zip.py` выполняется без проблем.
|
||||
|
||||
Для следующих двух вам (к сожалению) потребуется установить [Docker for Windows](https://docs.docker.com/desktop/setup/install/windows-install/).
|
||||
|
||||
**kmod**:
|
||||
```powershell
|
||||
$env:KMI="android12-5.10"; docker run --rm -it -v "${PWD}:/workspace" -e KMI=$env:KMI -w /workspace "ghcr.io/ylarod/ddk-min:$($env:KMI)-20260313" bash -c 'cd kmod && python3 ./build-zip.py --kmi $KMI'
|
||||
```
|
||||
Обязательно используйте ту же версию образа `ylarod/ddk-min` (дата после названия KMI), которая используется в `ci.yml`.
|
||||
|
||||
**zygisk**:
|
||||
```powershell
|
||||
docker run --rm -it -v "${PWD}:/workspace" -v "vpnhide_cargo_cache:/usr/local/cargo/registry" -w /workspace ghcr.io/okhsunrog/vpnhide/ci:latest bash -c 'cd zygisk && python3 ./build-zip.py'
|
||||
```
|
||||
Причина, по которой `zygisk` нельзя собрать напрямую, заключается в том, что исходный код зависимости `zygisk-api` содержит файл с именем `aux.rs`. Cargo использует `libgit2` для работы с git, в котором есть защита, запрещающая создавать файлы, _содержащие_ зарезервированные слова Windows. Вы получите ошибку: `cannot checkout to invalid path 'src/aux.rs'; class=Checkout (20)`. [Сообщают](https://superuser.com/a/1929659), что после какого-то обновления стало возможным создавать файлы, содержащие зарезервированные слова, **с** расширением, но, похоже, в `libgit2` это поведение не было изменено.
|
||||
|
||||
## Проверено на
|
||||
|
||||
- [RKNHardering](https://github.com/xtclovver/RKNHardering/) — все векторы обнаружения чисты
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ keytool -genkey -v -keystore ~/vpnhide.jks \
|
|||
### zygisk module
|
||||
|
||||
```sh
|
||||
cd zygisk && ./build-zip.sh
|
||||
cd zygisk && ./build-zip.py
|
||||
# → zygisk/target/vpnhide-zygisk.zip
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ Update-json **must** be committed *after* the GitHub release is **published** (i
|
|||
|
||||
## Build versions
|
||||
|
||||
Every packaging step runs `./scripts/build-version.sh` to compute the version string stamped into the artifact:
|
||||
Every packaging step runs `./scripts/build-version.py` to compute the version string stamped into the artifact:
|
||||
|
||||
- **On a release tag `vX.Y.Z`:** `X.Y.Z`
|
||||
- **N commits after the nearest tag:** `X.Y.Z-N-gSHA` (the git describe format)
|
||||
|
|
@ -61,6 +61,6 @@ This string goes into:
|
|||
- APK `versionName` (visible in Android Settings → Apps, diagnostic debug zip, `BuildConfig.VERSION_NAME`)
|
||||
- Inside the zip filenames (only for release tags; dev artifacts in CI keep a stable name)
|
||||
|
||||
The committed `module.prop` files are **not** modified — `build-zip.sh` stages a copy, patches the version there, and zips. `lsposed/app/build.gradle.kts` evaluates `build-version.sh` at configure time and sets `versionName` dynamically.
|
||||
The committed `module.prop` files are **not** modified — `build-zip.py` stages a copy, patches the version there, and zips. `lsposed/app/build.gradle.kts` evaluates `build-version.py` at configure time and sets `versionName` dynamically.
|
||||
|
||||
`versionCode` stays at the value baked in by the last `release.py` run (monotonically increasing integer required by Android/Magisk).
|
||||
|
|
|
|||
|
|
@ -15,16 +15,9 @@ KMI=android14-6.1
|
|||
# Build the kernel module (run from repo root)
|
||||
docker run --rm -v $(pwd)/kmod:/work \
|
||||
ghcr.io/ylarod/ddk-min:${KMI}-20260313 sh -c "
|
||||
CLANG=\$(echo /opt/ddk/clang/clang-r*/bin) && \
|
||||
make -C /opt/ddk/kdir/${KMI} M=/work \
|
||||
ARCH=arm64 LLVM=1 LLVM_IAS=1 \
|
||||
CC=\$CLANG/clang LD=\$CLANG/ld.lld \
|
||||
AR=\$CLANG/llvm-ar NM=\$CLANG/llvm-nm \
|
||||
OBJCOPY=\$CLANG/llvm-objcopy \
|
||||
OBJDUMP=\$CLANG/llvm-objdump \
|
||||
STRIP=\$CLANG/llvm-strip \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- \
|
||||
modules"
|
||||
CLANG_DIR=\$(echo /opt/ddk/clang/clang-r*/bin) \
|
||||
KERNEL_SRC=/opt/ddk/kdir/${KMI} \
|
||||
make"
|
||||
|
||||
# Package as KSU module
|
||||
cp kmod/vpnhide_kmod.ko kmod/module/
|
||||
|
|
@ -43,17 +36,11 @@ KMI=android14-6.1
|
|||
|
||||
podman run --rm --userns=keep-id -v "$(pwd)/kmod:/work:Z" \
|
||||
ghcr.io/ylarod/ddk-min:${KMI}-20260313 sh -c '
|
||||
CLANG=$(echo /opt/ddk/clang/clang-r*/bin) && \
|
||||
make -C /opt/ddk/kdir/'${KMI}' M=/work \
|
||||
ARCH=arm64 LLVM=1 LLVM_IAS=1 \
|
||||
CC=$CLANG/clang LD=$CLANG/ld.lld \
|
||||
AR=$CLANG/llvm-ar NM=$CLANG/llvm-nm \
|
||||
OBJCOPY=$CLANG/llvm-objcopy \
|
||||
OBJDUMP=$CLANG/llvm-objdump \
|
||||
STRIP=$CLANG/llvm-strip \
|
||||
CROSS_COMPILE=aarch64-linux-gnu- \
|
||||
modules'
|
||||
CLANG_DIR=\$(echo /opt/ddk/clang/clang-r*/bin) \
|
||||
KERNEL_SRC=/opt/ddk/kdir/${KMI} \
|
||||
make'
|
||||
|
||||
# Package as KSU module
|
||||
cp kmod/vpnhide_kmod.ko kmod/module/
|
||||
(cd kmod/module && zip -qr ../../vpnhide-kmod.zip .)
|
||||
```
|
||||
|
|
@ -70,7 +57,7 @@ cp .env.example .env
|
|||
# Edit .env with paths to your kernel source and clang toolchain
|
||||
direnv allow
|
||||
make
|
||||
./build-zip.sh
|
||||
./build-zip.py
|
||||
```
|
||||
|
||||
See `.env.example` for the required variables. You need a prepared kernel source tree with headers and `Module.symvers`.
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ MAKE_ARGS := -C $(KERNEL_SRC) M=$(CURDIR) \
|
|||
all:
|
||||
$(MAKE) $(MAKE_ARGS) modules
|
||||
|
||||
strip: all
|
||||
$(STRIP) -d $(CURDIR)/vpnhide_kmod.ko
|
||||
|
||||
clean:
|
||||
$(MAKE) $(MAKE_ARGS) clean
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ CI builds are provided for all 7 GKI generations: `android12-5.10` through `andr
|
|||
See [BUILDING.md](BUILDING.md) for the full guide (DDK Docker build, kernel source preparation, toolchain setup, `Module.symvers` generation).
|
||||
|
||||
```bash
|
||||
cd kmod && ./build-zip.sh
|
||||
cd kmod && ./build-zip.py
|
||||
```
|
||||
|
||||
## Install
|
||||
|
|
|
|||
166
kmod/build-zip.py
Executable file
166
kmod/build-zip.py
Executable file
|
|
@ -0,0 +1,166 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build and package the KernelSU/Magisk kernel module zip.
|
||||
|
||||
Assembles a module staging directory so the committed module.prop stays at
|
||||
its release version while the zip carries the actual build version (git describe).
|
||||
|
||||
Usage:
|
||||
python3 build-zip.py --kdir /path/to/kdir --kmi android14-5.15 # explicit args
|
||||
python3 build-zip.py --kdir /path/to/kdir --kmi android14-5.15 --out custom.zip # custom output
|
||||
# Or use environment variables: KDIR and KMI (CLI args override env vars)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip # type: ignore[import-not-found]
|
||||
|
||||
|
||||
# Module file names
|
||||
KMOD_C = "vpnhide_kmod.c"
|
||||
KMOD_KO = "vpnhide_kmod.ko"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build and package the kernel module zip.")
|
||||
parser.add_argument(
|
||||
"--kdir",
|
||||
type=str,
|
||||
help="Kernel source directory (overrides KDIR or KERNEL_SRC env var)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--kmi",
|
||||
type=str,
|
||||
help="Kernel module interface variant (e.g., android14-5.15) for module.prop (overrides KMI env var)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out",
|
||||
type=str,
|
||||
help="Output zip filename (default: vpnhide-kmod-<kmi>.zip)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clang-dir",
|
||||
type=str,
|
||||
help="Clang binaries directory (overrides CLANG_DIR env var or auto-detects)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
kmod_dir = Path(__file__).resolve().parent
|
||||
os.chdir(kmod_dir)
|
||||
|
||||
# Resolve kdir: CLI arg > KDIR env var > KERNEL_SRC env var
|
||||
if args.kdir:
|
||||
kdir = args.kdir
|
||||
kdir_src = "--kdir CLI argument"
|
||||
else:
|
||||
kdir = os.environ.get("KDIR")
|
||||
kdir_src = "KDIR env var"
|
||||
if not kdir:
|
||||
kdir = os.environ.get("KERNEL_SRC")
|
||||
kdir_src = "KERNEL_SRC env var"
|
||||
if not kdir:
|
||||
print("Error: --kdir argument, KDIR, or KERNEL_SRC env var is required", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Using kdir from {kdir_src}: {kdir}")
|
||||
|
||||
# Resolve kmi: CLI arg > KMI env var
|
||||
if args.kmi:
|
||||
kmi = args.kmi
|
||||
kmi_src = "--kmi CLI argument"
|
||||
else:
|
||||
kmi = os.environ.get("KMI")
|
||||
kmi_src = "KMI env var"
|
||||
if not kmi:
|
||||
print("Error: --kmi argument or KMI env var is required", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"Using kmi from {kmi_src}: {kmi}")
|
||||
|
||||
# Set up environment for make
|
||||
os.environ["KERNEL_SRC"] = kdir
|
||||
|
||||
# Resolve clang_dir: CLI arg > CLANG_DIR env var > auto-detect
|
||||
clang_dir = None
|
||||
clang_dir_src = None
|
||||
if args.clang_dir:
|
||||
clang_dir = args.clang_dir
|
||||
clang_dir_src = "--clang-dir CLI argument"
|
||||
else:
|
||||
clang_dir = os.environ.get("CLANG_DIR")
|
||||
if clang_dir:
|
||||
clang_dir_src = "CLANG_DIR env var"
|
||||
else:
|
||||
# Auto-detect: In CI, clang is at /opt/ddk/clang/clang-r*/bin
|
||||
clang_base = Path("/opt/ddk/clang")
|
||||
if clang_base.exists():
|
||||
clang_dirs = sorted(d for d in clang_base.iterdir() if d.is_dir() and d.name.startswith("clang-"))
|
||||
if clang_dirs:
|
||||
clang_dir = str(clang_dirs[-1] / "bin")
|
||||
clang_dir_src = "auto-detected from /opt/ddk/clang"
|
||||
if clang_dir:
|
||||
os.environ["CLANG_DIR"] = clang_dir
|
||||
print(f"Using clang-dir from {clang_dir_src}: {clang_dir}")
|
||||
else:
|
||||
print("Warning: clang-dir not set, using system PATH", file=sys.stderr)
|
||||
|
||||
# Build the kernel module (env vars loaded by direnv from .env)
|
||||
kmod_c = kmod_dir / KMOD_C
|
||||
kmod_ko = kmod_dir / KMOD_KO
|
||||
|
||||
if not kmod_ko.exists() or kmod_c.stat().st_mtime > kmod_ko.stat().st_mtime:
|
||||
print("Building kernel module...")
|
||||
subprocess.run(["make", "strip"], check=True)
|
||||
|
||||
# Assemble the module staging directory so the committed module.prop
|
||||
# stays at its release version while the zip carries the actual build
|
||||
# version (git describe).
|
||||
staging = kmod_dir / "module-staging"
|
||||
if staging.exists():
|
||||
shutil.rmtree(staging)
|
||||
shutil.copytree(kmod_dir / "module", staging)
|
||||
shutil.copy(kmod_dir / KMOD_KO, staging / KMOD_KO)
|
||||
|
||||
# Get build version
|
||||
build_version = get_build_version(kmod_dir.parent)
|
||||
|
||||
# Stamp version into module.prop
|
||||
module_prop = staging / "module.prop"
|
||||
content = module_prop.read_text(encoding="utf-8")
|
||||
content = re.sub(r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE)
|
||||
# Add gkiVariant and updateJson
|
||||
content = re.sub(r"^gkiVariant=.*", f"gkiVariant={kmi}", content, flags=re.MULTILINE)
|
||||
if not re.search(r"^gkiVariant=", content, flags=re.MULTILINE):
|
||||
content = content.rstrip() + f"\ngkiVariant={kmi}\n"
|
||||
update_json_url = f"https://raw.githubusercontent.com/okhsunrog/vpnhide/main/update-json/update-kmod-{kmi}.json"
|
||||
content = re.sub(r"^updateJson=.*", f"updateJson={update_json_url}", content, flags=re.MULTILINE)
|
||||
if not re.search(r"^updateJson=", content, flags=re.MULTILINE):
|
||||
content = content.rstrip() + f"\nupdateJson={update_json_url}\n"
|
||||
module_prop.write_text(content, encoding="utf-8")
|
||||
print(f"Stamped module.prop version=v{build_version} gkiVariant={kmi}")
|
||||
|
||||
# Create zip in parent directory (workspace root for CI)
|
||||
out_zip = kmod_dir.parent / (args.out if args.out else f"vpnhide-kmod-{kmi}.zip")
|
||||
if out_zip.exists():
|
||||
out_zip.unlink()
|
||||
|
||||
make_zip(staging, out_zip)
|
||||
shutil.rmtree(staging)
|
||||
|
||||
print()
|
||||
print(f"Built: {out_zip.name}")
|
||||
size_kb = out_zip.stat().st_size / 1024
|
||||
print(f" {out_zip} ({size_kb:.1f} KB)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Build the kernel module (env vars loaded by direnv from .env)
|
||||
if [ ! -f vpnhide_kmod.ko ] || [ vpnhide_kmod.c -nt vpnhide_kmod.ko ]; then
|
||||
echo "Building kernel module..."
|
||||
make
|
||||
fi
|
||||
|
||||
# Assemble the module staging directory so the committed module.prop
|
||||
# stays at its release version while the zip carries the actual build
|
||||
# version (git describe).
|
||||
STAGING="module-staging"
|
||||
rm -rf "$STAGING"
|
||||
cp -a module "$STAGING"
|
||||
cp vpnhide_kmod.ko "$STAGING/vpnhide_kmod.ko"
|
||||
|
||||
BUILD_VERSION="$(../scripts/build-version.sh)"
|
||||
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" "$STAGING/module.prop"
|
||||
echo "Stamped module.prop version=v${BUILD_VERSION}"
|
||||
|
||||
OUT="vpnhide-kmod.zip"
|
||||
rm -f "$OUT"
|
||||
(cd "$STAGING" && zip -qr "../$OUT" .)
|
||||
rm -rf "$STAGING"
|
||||
|
||||
echo
|
||||
echo "Built: $OUT"
|
||||
ls -lh "$OUT"
|
||||
|
|
@ -11,16 +11,22 @@ android {
|
|||
namespace = "dev.okhsunrog.vpnhide"
|
||||
compileSdk = 35
|
||||
|
||||
// Effective build version from ../scripts/build-version.sh:
|
||||
// Effective build version from ../scripts/build-version.py:
|
||||
// release tag -> "0.6.2"
|
||||
// dev build -> "0.6.1-5-gabc1234" (+"-dirty" if uncommitted)
|
||||
// no git -> VERSION file
|
||||
// Python instead of bash so Windows contributors can build without WSL.
|
||||
// Script is stdlib-only — no `uv` / pip install needed. `python` on
|
||||
// Windows, `python3` elsewhere: Ubuntu 22.04+ ships only the latter,
|
||||
// Windows python.org / Store installer ships only the former.
|
||||
val isWindows = System.getProperty("os.name").lowercase().contains("windows")
|
||||
val pythonExe = if (isWindows) "python" else "python3"
|
||||
val buildVersion: String =
|
||||
providers
|
||||
.exec {
|
||||
commandLine(
|
||||
"bash",
|
||||
rootProject.projectDir.parentFile.resolve("scripts/build-version.sh").absolutePath,
|
||||
pythonExe,
|
||||
rootProject.projectDir.parentFile.resolve("scripts/build-version.py").absolutePath,
|
||||
)
|
||||
}.standardOutput.asText
|
||||
.get()
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class BaseVersionTest {
|
|||
|
||||
@Test
|
||||
fun `dirty suffix alone is stripped`() {
|
||||
// build-version.sh emits `X.Y.Z-dirty` when HEAD is on a tag but
|
||||
// build-version.py emits `X.Y.Z-dirty` when HEAD is on a tag but
|
||||
// the working tree has uncommitted changes.
|
||||
assertEquals("0.6.2", baseVersion("0.6.2-dirty"))
|
||||
assertEquals("0.6.2", baseVersion("v0.6.2-dirty"))
|
||||
|
|
|
|||
65
portshide/build-zip.py
Executable file
65
portshide/build-zip.py
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build and package the ports-hiding Magisk/KernelSU module zip.
|
||||
|
||||
Assembles a module staging directory so the committed module.prop stays at
|
||||
its release version while the zip carries the actual build version (git describe).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip # type: ignore[import-not-found]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
os.chdir(script_dir)
|
||||
|
||||
# Assemble the module staging directory so the committed module.prop
|
||||
# stays at its release version while the zip carries the actual build
|
||||
# version (git describe).
|
||||
staging = script_dir / "module-staging"
|
||||
if staging.exists():
|
||||
shutil.rmtree(staging)
|
||||
shutil.copytree(script_dir / "module", staging)
|
||||
|
||||
# Get build version
|
||||
build_version = get_build_version(script_dir.parent)
|
||||
|
||||
# Stamp version into module.prop
|
||||
module_prop = staging / "module.prop"
|
||||
content = module_prop.read_text(encoding="utf-8")
|
||||
content = re.sub(r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE)
|
||||
module_prop.write_text(content, encoding="utf-8")
|
||||
print(f"Stamped module.prop version=v{build_version}")
|
||||
|
||||
# CI sets UPDATE_JSON_URL so Magisk/KSU knows where to check for updates;
|
||||
# local dev builds leave it unset and ship without updateJson.
|
||||
update_json_url = os.environ.get("UPDATE_JSON_URL")
|
||||
if update_json_url:
|
||||
with open(module_prop, "a", encoding="utf-8") as f:
|
||||
f.write(f"updateJson={update_json_url}\n")
|
||||
|
||||
# Create zip
|
||||
out_zip = script_dir / "vpnhide-ports.zip"
|
||||
if out_zip.exists():
|
||||
out_zip.unlink()
|
||||
|
||||
make_zip(staging, out_zip)
|
||||
shutil.rmtree(staging)
|
||||
|
||||
print()
|
||||
print(f"Built: {out_zip.name}")
|
||||
size_kb = out_zip.stat().st_size / 1024
|
||||
print(f" {out_zip} ({size_kb:.1f} KB)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Assemble the module staging directory so the committed module.prop
|
||||
# stays at its release version while the zip carries the actual build
|
||||
# version (git describe).
|
||||
STAGING="module-staging"
|
||||
rm -rf "$STAGING"
|
||||
cp -a module "$STAGING"
|
||||
|
||||
BUILD_VERSION="$(../scripts/build-version.sh)"
|
||||
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" "$STAGING/module.prop"
|
||||
echo "Stamped module.prop version=v${BUILD_VERSION}"
|
||||
|
||||
# CI sets UPDATE_JSON_URL so Magisk/KSU knows where to check for updates;
|
||||
# local dev builds leave it unset and ship without updateJson.
|
||||
if [ -n "${UPDATE_JSON_URL:-}" ]; then
|
||||
echo "updateJson=${UPDATE_JSON_URL}" >> "$STAGING/module.prop"
|
||||
fi
|
||||
|
||||
OUT="vpnhide-ports.zip"
|
||||
rm -f "$OUT"
|
||||
(cd "$STAGING" && zip -qr "../$OUT" .)
|
||||
rm -rf "$STAGING"
|
||||
|
||||
echo
|
||||
echo "Built: $OUT"
|
||||
ls -lh "$OUT"
|
||||
24
scripts/build-version.py
Executable file
24
scripts/build-version.py
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Print the effective build version for vpnhide artifacts.
|
||||
|
||||
Used by every packaging step (module.prop, APK versionName, CI
|
||||
artifact names) so dev builds are unambiguously identifiable at a
|
||||
glance. Called from `app/build.gradle.kts` on every Gradle build, so
|
||||
stays on stdlib only — Gradle shouldn't need `uv` / external deps to
|
||||
assemble the APK.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from build_lib import get_build_version
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print(get_build_version())
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Print the effective build version for vpnhide artifacts.
|
||||
#
|
||||
# - HEAD on a tag vX.Y.Z -> "X.Y.Z" (release build)
|
||||
# - N commits after tag vX.Y.Z -> "X.Y.Z-N-gSHA" (dev build)
|
||||
# - working tree dirty -> additional "-dirty" suffix
|
||||
# - no git / no matching tag -> falls back to VERSION file
|
||||
#
|
||||
# Used by every packaging step (module.prop, APK versionName, CI artifact
|
||||
# names) so dev builds are unambiguously identifiable at a glance.
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if git rev-parse --git-dir >/dev/null 2>&1 \
|
||||
&& raw=$(git describe --tags --match 'v*' --dirty 2>/dev/null); then
|
||||
echo "${raw#v}"
|
||||
else
|
||||
cat VERSION
|
||||
fi
|
||||
50
scripts/build_lib.py
Normal file
50
scripts/build_lib.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Shared helpers for build scripts.
|
||||
|
||||
Used by kmod/build-zip.py, portshide/build-zip.py, and zygisk/build-zip.py.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_python_exe() -> str:
|
||||
"""Return 'python' on Windows, 'python3' elsewhere."""
|
||||
return "python" if os.name == "nt" else "python3"
|
||||
|
||||
|
||||
def make_zip(source_dir: Path, output_zip: Path) -> None:
|
||||
"""Create a zip archive from source_dir contents."""
|
||||
with zipfile.ZipFile(output_zip, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for file_path in source_dir.rglob("*"):
|
||||
if file_path.is_file():
|
||||
arcname = file_path.relative_to(source_dir)
|
||||
zf.write(file_path, arcname)
|
||||
|
||||
|
||||
def get_build_version(repo_root: Path | None = None) -> str:
|
||||
"""Get the effective build version for vpnhide artifacts.
|
||||
|
||||
- HEAD on a tag vX.Y.Z -> "X.Y.Z" (release build)
|
||||
- N commits after tag vX.Y.Z -> "X.Y.Z-N-gSHA" (dev build)
|
||||
- working tree dirty -> additional "-dirty" suffix
|
||||
- no git / no matching tag -> falls back to VERSION file
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
if repo_root is None:
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
|
||||
result = subprocess.run(
|
||||
["git", "describe", "--tags", "--match", "v*", "--dirty"],
|
||||
cwd=repo_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
return result.stdout.strip().removeprefix("v")
|
||||
|
||||
version_file = repo_root / "VERSION"
|
||||
return version_file.read_text(encoding="utf-8").strip()
|
||||
|
|
@ -74,7 +74,7 @@ Requirements:
|
|||
Build and package:
|
||||
|
||||
```bash
|
||||
./build-zip.sh
|
||||
./build-zip.py
|
||||
# Output: target/vpnhide-zygisk.zip (~180 KB)
|
||||
```
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ VPN interface prefixes: `tun`, `ppp`, `tap`, `wg`, `ipsec`, `xfrm`, `utun`, `l2t
|
|||
- `build.rs` -- drives CMake on the shadowhook submodule
|
||||
- `third_party/android-inline-hook/` -- submodule (our shadowhook fork)
|
||||
- `module/` -- KernelSU/Magisk module metadata
|
||||
- `build-zip.sh` -- cross-compile + package script
|
||||
- `build-zip.py` -- cross-compile + package script
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
102
zygisk/build-zip.py
Executable file
102
zygisk/build-zip.py
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Build the native library for aarch64 Android and package it into an
|
||||
installable KernelSU/Magisk module zip.
|
||||
|
||||
Requirements:
|
||||
- rustup target aarch64-linux-android (already installed)
|
||||
- cargo-ndk
|
||||
- Android NDK at $ANDROID_NDK_HOME or auto-detected from $HOME/Android/Sdk/ndk/*
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip # type: ignore[import-not-found]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
os.chdir(script_dir)
|
||||
|
||||
# Auto-detect NDK if ANDROID_NDK_HOME isn't set
|
||||
android_ndk_home = os.environ.get("ANDROID_NDK_HOME")
|
||||
if not android_ndk_home:
|
||||
ndk_base = Path.home() / "Android" / "Sdk" / "ndk"
|
||||
if ndk_base.exists():
|
||||
ndk_versions = sorted(
|
||||
d.name for d in ndk_base.iterdir() if d.is_dir()
|
||||
)
|
||||
if ndk_versions:
|
||||
android_ndk_home = str(ndk_base / ndk_versions[-1])
|
||||
|
||||
if not android_ndk_home or not Path(android_ndk_home).is_dir():
|
||||
print(
|
||||
"error: ANDROID_NDK_HOME not set and no NDK found under ~/Android/Sdk/ndk",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
|
||||
print(f"Using NDK: {android_ndk_home}")
|
||||
os.environ["ANDROID_NDK_HOME"] = android_ndk_home
|
||||
|
||||
# Build the cdylib for arm64-v8a
|
||||
subprocess.run(
|
||||
["cargo", "ndk", "-t", "arm64-v8a", "build", "--release"],
|
||||
check=True,
|
||||
)
|
||||
|
||||
so_src = script_dir / "target" / "aarch64-linux-android" / "release" / "libvpnhide_zygisk.so"
|
||||
if not so_src.exists():
|
||||
print(f"error: expected {so_src} after cargo ndk build, not found", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Assemble the module staging directory
|
||||
staging = script_dir / "target" / "module-staging"
|
||||
if staging.exists():
|
||||
shutil.rmtree(staging)
|
||||
shutil.copytree(script_dir / "module", staging)
|
||||
(staging / "zygisk").mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(so_src, staging / "zygisk" / "arm64-v8a.so")
|
||||
|
||||
# Get build version
|
||||
build_version = get_build_version(script_dir.parent)
|
||||
|
||||
# Stamp the effective build version into the staging module.prop without
|
||||
# touching the committed file. On a release tag this matches VERSION; on
|
||||
# any other commit the git suffix makes dev builds identifiable.
|
||||
module_prop = staging / "module.prop"
|
||||
content = module_prop.read_text(encoding="utf-8")
|
||||
content = re.sub(r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE)
|
||||
module_prop.write_text(content, encoding="utf-8")
|
||||
print(f"Stamped module.prop version=v{build_version}")
|
||||
|
||||
# CI sets UPDATE_JSON_URL so Magisk/KSU knows where to check for updates;
|
||||
# local dev builds leave it unset and ship without updateJson.
|
||||
update_json_url = os.environ.get("UPDATE_JSON_URL")
|
||||
if update_json_url:
|
||||
with open(module_prop, "a", encoding="utf-8") as f:
|
||||
f.write(f"updateJson={update_json_url}\n")
|
||||
|
||||
# Zip it
|
||||
out_zip = script_dir / "target" / "vpnhide-zygisk.zip"
|
||||
if out_zip.exists():
|
||||
out_zip.unlink()
|
||||
|
||||
make_zip(staging, out_zip)
|
||||
|
||||
print()
|
||||
print(f"Built: {out_zip.name}")
|
||||
size_kb = out_zip.stat().st_size / 1024
|
||||
print(f" {out_zip} ({size_kb:.1f} KB)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build the native library for aarch64 Android and package it into an
|
||||
# installable KernelSU/Magisk module zip.
|
||||
#
|
||||
# Requirements:
|
||||
# - rustup target aarch64-linux-android (already installed)
|
||||
# - cargo-ndk
|
||||
# - Android NDK at $ANDROID_NDK_HOME or auto-detected from $HOME/Android/Sdk/ndk/*
|
||||
# - zip
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Auto-detect NDK if ANDROID_NDK_HOME isn't set
|
||||
if [ -z "${ANDROID_NDK_HOME:-}" ]; then
|
||||
ANDROID_NDK_HOME="$(find "$HOME/Android/Sdk/ndk" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort -V | tail -1)"
|
||||
fi
|
||||
if [ -z "${ANDROID_NDK_HOME:-}" ] || [ ! -d "$ANDROID_NDK_HOME" ]; then
|
||||
echo "error: ANDROID_NDK_HOME not set and no NDK found under ~/Android/Sdk/ndk" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Using NDK: $ANDROID_NDK_HOME"
|
||||
export ANDROID_NDK_HOME
|
||||
|
||||
# Build the cdylib for arm64-v8a
|
||||
cargo ndk -t arm64-v8a build --release
|
||||
|
||||
SO_SRC="target/aarch64-linux-android/release/libvpnhide_zygisk.so"
|
||||
if [ ! -f "$SO_SRC" ]; then
|
||||
echo "error: expected $SO_SRC after cargo ndk build, not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Assemble the module staging directory
|
||||
STAGING="target/module-staging"
|
||||
rm -rf "$STAGING"
|
||||
cp -a module "$STAGING"
|
||||
mkdir -p "$STAGING/zygisk"
|
||||
cp "$SO_SRC" "$STAGING/zygisk/arm64-v8a.so"
|
||||
|
||||
# Stamp the effective build version into the staging module.prop without
|
||||
# touching the committed file. On a release tag this matches VERSION; on
|
||||
# any other commit the git suffix makes dev builds identifiable.
|
||||
BUILD_VERSION="$(../scripts/build-version.sh)"
|
||||
sed -i "s|^version=.*|version=v${BUILD_VERSION}|" "$STAGING/module.prop"
|
||||
echo "Stamped module.prop version=v${BUILD_VERSION}"
|
||||
|
||||
# CI sets UPDATE_JSON_URL so Magisk/KSU knows where to check for updates;
|
||||
# local dev builds leave it unset and ship without updateJson.
|
||||
if [ -n "${UPDATE_JSON_URL:-}" ]; then
|
||||
echo "updateJson=${UPDATE_JSON_URL}" >> "$STAGING/module.prop"
|
||||
fi
|
||||
|
||||
# Zip it
|
||||
OUT_ZIP="target/vpnhide-zygisk.zip"
|
||||
rm -f "$OUT_ZIP"
|
||||
(cd "$STAGING" && zip -qr "../../$OUT_ZIP" .)
|
||||
|
||||
echo
|
||||
echo "Built: $OUT_ZIP"
|
||||
ls -lh "$OUT_ZIP"
|
||||
Loading…
Add table
Add a link
Reference in a new issue