Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ jobs:

- name: Install gomobile
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
# Use the golang.org/x/mobile version go.mod already requires (tidy.yml
# updates it with each xray-core bump) rather than whatever @latest is
# at build time, so rebuilding a tag later uses the same gomobile.
go install golang.org/x/mobile/cmd/gomobile@$(go list -m -f '{{.Version}}' golang.org/x/mobile)
export PATH=$PATH:~/go/bin

- name: Setup Android SDK
Expand All @@ -45,6 +48,18 @@ jobs:
--install "ndk;29.0.14206865"
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> $GITHUB_ENV

# gomobile compiles the aar's Java bindings (classes.jar) with javac, so the
# JDK is part of the output. Without this the runner image's default JDK
# is used, which changes whenever GitHub updates the image. This is the
# Temurin 17.0.20.1+1 that ubuntu-latest defaults to today, spelled as the
# Adoptium semver setup-java matches ('17.0.20.1' would only match the
# preinstalled copy by accident, and stop matching once the image moves).
- name: Setup Java
uses: actions/setup-java@v6.0.0
with:
distribution: 'temurin'
java-version: '17.0.20+101'

- name: Build
run: |
mkdir -p assets data
Expand All @@ -53,6 +68,13 @@ jobs:
gomobile init
go mod tidy
gomobile bind -v -androidapi 24 -trimpath -ldflags='-s -w -buildid= -checklinkname=0' ./
# What a rebuild of this release needs beyond the tagged source: the geo
# data resolved above, and the exact Go release setup-go picked for
# go.mod's `go` line. Published next to the aar.
{ cat data/geo-assets.lock; echo "GO_VERSION='$(go env GOVERSION)'"; } > libv2ray-build.lock
cat libv2ray-build.lock
# Checksums of what this run built, to compare a rebuild against
sha256sum libv2ray*r

- name: Upload build artifacts
if: github.event.inputs.release_tag == ''
Expand All @@ -61,6 +83,7 @@ jobs:
name: libv2ray
path: |
${{ github.workspace }}/libv2ray*r
${{ github.workspace }}/libv2ray-build.lock

- name: Upload AndroidLibXrayLite to release
if: github.event.inputs.release_tag != ''
Expand All @@ -69,3 +92,10 @@ jobs:
file: ./libv2ray*r
tag: ${{ github.event.inputs.release_tag }}
file_glob: true

- name: Upload build lock to release
if: github.event.inputs.release_tag != ''
uses: svenstaro/upload-release-action@v2
with:
file: ./libv2ray-build.lock
tag: ${{ github.event.inputs.release_tag }}
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@
2. `gomobile init`
3. `go mod tidy -v`
4. `gomobile bind -v -androidapi 24 -trimpath -ldflags='-s -w -buildid= -checklinkname=0' ./`

## Reproducing a release
Each release publishes a `libv2ray-build.lock` next to `libv2ray.aar`, recording
the inputs its tagged source does not pin by itself: the Go release it was built
with, and the exact geo data embedded in `assets/`. To rebuild a release's aar
byte for byte:

1. Check out the release tag, and use the JDK, NDK and Android SDK versions named in `.github/workflows/main.yml`
2. Install the Go release named by `GO_VERSION` in `libv2ray-build.lock`
3. `mkdir -p assets data && bash gen_assets.sh pinned libv2ray-build.lock && cp data/*.dat assets/`
4. `go install golang.org/x/mobile/cmd/gomobile@$(go list -m -f '{{.Version}}' golang.org/x/mobile)`
5. `gomobile init`, `go mod tidy -v`, then the `gomobile bind` command from step 4 above
88 changes: 80 additions & 8 deletions gen_assets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,101 @@ __file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename "${__file}" .sh)"

DATADIR="${__dir}/data"
# Written by every run: each source resolved to an exact release tag or commit,
# with the SHA-256 of what was downloaded. Published with the aar, it lets a
# release be rebuilt with the same data via `gen_assets.sh pinned <file>`.
LOCKFILE="${DATADIR}/geo-assets.lock"

RULES_REPO="Loyalsoldier/v2ray-rules-dat"
GEOIP_REPO="Loyalsoldier/geoip"


# Check for required dependencies
check_dependencies() {
command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but it's not installed. Aborting."; exit 1; }
command -v go >/dev/null 2>&1 || { echo >&2 "Go is required but it's not installed. Aborting."; exit 1; }
command -v git >/dev/null 2>&1 || { echo >&2 "git is required but it's not installed. Aborting."; exit 1; }
}


# Download data function
download_dat() {
# The tag that github.com/<repo>/releases/latest currently redirects to
latest_release_tag() {
curl -fsSI "https://github.com/$1/releases/latest" | awk -F/ 'tolower($0) ~ /^location:/ {print $NF}' | tr -d '\r'
}

# The commit a branch currently points at
branch_commit() {
git ls-remote "https://github.com/$1.git" "refs/heads/$2" | cut -f1
}

sha256() {
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1"; else shasum -a 256 "$1"; fi | cut -d' ' -f1
}

# fetch <url> <file> [expected sha256]: download, verify if a checksum is
# given, and print the file's checksum
fetch() {
curl -fsSL "$1" -o "$2"
local sum
sum=$(sha256 "$2")
if [[ -n "${3:-}" && "$sum" != "$3" ]]; then
echo >&2 "Checksum mismatch for $2: expected $3, got $sum"
exit 1
fi
echo "$sum"
}

# Download from the sources named by RULES_DAT_TAG and GEOIP_RELEASE_COMMIT,
# verify against any *_SHA256 already set, and record what was fetched
fetch_dat() {
if [[ ! -d "$DATADIR" ]]; then
echo "Downloading failed \"$DATADIR\" does not exists"
exit 1
fi

echo "Downloading geoip.dat..."
curl -sL https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat -o "$DATADIR/geoip.dat"
echo "Downloading geoip.dat from $RULES_REPO $RULES_DAT_TAG..."
GEOIP_DAT_SHA256=$(fetch "https://github.com/$RULES_REPO/releases/download/$RULES_DAT_TAG/geoip.dat" "$DATADIR/geoip.dat" "${GEOIP_DAT_SHA256:-}")

echo "Downloading geosite.dat from $RULES_REPO $RULES_DAT_TAG..."
GEOSITE_DAT_SHA256=$(fetch "https://github.com/$RULES_REPO/releases/download/$RULES_DAT_TAG/geosite.dat" "$DATADIR/geosite.dat" "${GEOSITE_DAT_SHA256:-}")

echo "Downloading geoip-only-cn-private.dat from $GEOIP_REPO $GEOIP_RELEASE_COMMIT..."
GEOIP_ONLY_CN_PRIVATE_DAT_SHA256=$(fetch "https://raw.githubusercontent.com/$GEOIP_REPO/$GEOIP_RELEASE_COMMIT/geoip-only-cn-private.dat" "$DATADIR/geoip-only-cn-private.dat" "${GEOIP_ONLY_CN_PRIVATE_DAT_SHA256:-}")

echo "Downloading geosite.dat..."
curl -sL https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat -o "$DATADIR/geosite.dat"
cat > "$LOCKFILE" <<EOF
RULES_DAT_TAG='$RULES_DAT_TAG'
GEOIP_DAT_SHA256='$GEOIP_DAT_SHA256'
GEOSITE_DAT_SHA256='$GEOSITE_DAT_SHA256'
GEOIP_RELEASE_COMMIT='$GEOIP_RELEASE_COMMIT'
GEOIP_ONLY_CN_PRIVATE_DAT_SHA256='$GEOIP_ONLY_CN_PRIVATE_DAT_SHA256'
EOF
echo "Recorded the exact sources in $LOCKFILE"
}

echo "Downloading geoip-only-cn-private.dat..."
curl -sL https://raw.githubusercontent.com/Loyalsoldier/geoip/release/geoip-only-cn-private.dat -o "$DATADIR/geoip-only-cn-private.dat"
# Download data function: the newest data, as before, but resolved to an
# exact tag and commit first so that what was downloaded can be recorded
download_dat() {
RULES_DAT_TAG=$(latest_release_tag "$RULES_REPO")
GEOIP_RELEASE_COMMIT=$(branch_commit "$GEOIP_REPO" release)
if [[ -z "$RULES_DAT_TAG" || -z "$GEOIP_RELEASE_COMMIT" ]]; then
echo >&2 "Could not resolve the latest geo data sources. Aborting."
exit 1
fi
fetch_dat
}

# Exactly the data recorded in a lock file, for rebuilding a release
pinned_dat() {
local lock="${1:-}"
if [[ -z "$lock" ]]; then
echo >&2 "Usage: gen_assets.sh pinned <lock file>"
exit 1
fi
# `.` searches PATH for a bare file name; make sure the given file is read
[[ "$lock" == */* ]] || lock="./$lock"
# shellcheck source=/dev/null
. "$lock"
fetch_dat
}

# Main execution logic
Expand All @@ -43,5 +114,6 @@ check_dependencies

case $ACTION in
"download") download_dat ;;
"pinned") pinned_dat "${2:-}" ;;
*) echo "Invalid action: $ACTION" ; exit 1 ;;
esac