Skip to content

Upstream watch

Upstream watch #4

name: Upstream watch
# Daily sweep: queries the Go module proxy for the latest tags of the
# three core deps (mihomo, sing-box, xray-core). If any have moved
# beyond what's pinned in go/go.mod, bump them, rebuild the aar, tag a
# v<YYYY.MM.DD> release, and publish the aar as a GitHub Release
# asset. Multiple bumps on the same day are batched into one release.
# Same-day re-runs (manual force) append .1, .2… to the tag.
on:
schedule:
- cron: '0 8 * * *' # 08:00 UTC daily
workflow_dispatch:
inputs:
force_release:
description: "Cut a release even if no upstream change is detected"
type: boolean
default: false
concurrency:
group: upstream-watch
cancel-in-progress: false
permissions:
contents: write
jobs:
watch-and-release:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
persist-credentials: true
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go/go.mod
cache: true
cache-dependency-path: go/go.sum
- name: Point gomobile at the runner's NDK
run: |
set -euo pipefail
# ubuntu-latest images ship an Android SDK + NDK; gomobile
# resolves the NDK from ANDROID_NDK_HOME.
echo "ANDROID_NDK_HOME=$ANDROID_NDK_LATEST_HOME" >> "$GITHUB_ENV"
- name: Install gomobile
run: |
set -euo pipefail
go install golang.org/x/mobile/cmd/gomobile@latest
go install golang.org/x/mobile/cmd/gobind@latest
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- name: Detect upstream versions
id: versions
working-directory: go
run: |
set -euo pipefail
# The Go proxy's /@latest endpoint is defined to return the
# newest *stable* tag (no pre-releases) for a module. We
# query it directly rather than `go list -m -versions`, which
# would happily include `v1.14.0-alpha.23` in its output.
# For xray-core this returns the Go-module-compatible
# v1.YYMMDD.0 tag, not the human v26.x.x.
latest() {
curl -sSf "https://proxy.golang.org/$1/@latest" | jq -r '.Version'
}
# Current pin = the version go.mod's require block resolves to.
current() { go list -m -f '{{.Version}}' "$1"; }
mihomo_latest=$(latest github.com/metacubex/mihomo)
singbox_latest=$(latest github.com/sagernet/sing-box)
xray_latest=$(latest github.com/xtls/xray-core)
mihomo_current=$(current github.com/metacubex/mihomo)
singbox_current=$(current github.com/sagernet/sing-box)
xray_current=$(current github.com/xtls/xray-core)
{
echo "### Upstream pin check"
echo
echo "| module | current | latest | bump? |"
echo "|---|---|---|---|"
for trio in \
"mihomo|$mihomo_current|$mihomo_latest" \
"sing-box|$singbox_current|$singbox_latest" \
"xray-core|$xray_current|$xray_latest"; do
IFS='|' read -r name cur lat <<<"$trio"
[[ "$cur" == "$lat" ]] && marker="—" || marker="✓"
echo "| $name | $cur | $lat | $marker |"
done
} >> "$GITHUB_STEP_SUMMARY"
changed=false
[[ "$mihomo_current" != "$mihomo_latest" ]] && changed=true
[[ "$singbox_current" != "$singbox_latest" ]] && changed=true
[[ "$xray_current" != "$xray_latest" ]] && changed=true
{
echo "mihomo_latest=$mihomo_latest"
echo "singbox_latest=$singbox_latest"
echo "xray_latest=$xray_latest"
echo "mihomo_current=$mihomo_current"
echo "singbox_current=$singbox_current"
echo "xray_current=$xray_current"
echo "changed=$changed"
} >> "$GITHUB_OUTPUT"
- name: Decide whether to proceed
id: proceed
run: |
if [[ "${{ steps.versions.outputs.changed }}" == "true" \
|| "${{ inputs.force_release }}" == "true" ]]; then
echo "go=true" >> "$GITHUB_OUTPUT"
else
echo "go=false" >> "$GITHUB_OUTPUT"
echo "::notice::No upstream changes; skipping release."
fi
- name: Bump go.mod
if: steps.proceed.outputs.go == 'true'
working-directory: go
run: |
set -euo pipefail
go get github.com/metacubex/mihomo@${{ steps.versions.outputs.mihomo_latest }}
go get github.com/sagernet/sing-box@${{ steps.versions.outputs.singbox_latest }}
go get github.com/xtls/xray-core@${{ steps.versions.outputs.xray_latest }}
go mod tidy
- name: Build aar
if: steps.proceed.outputs.go == 'true'
run: Scripts/build.sh
- name: Compute release tag
if: steps.proceed.outputs.go == 'true'
id: tag
run: |
set -euo pipefail
base="v$(date -u +%Y.%m.%d)"
tag="$base"
n=1
# Already taken? Append .1, .2, … (manual force on the same day).
while git rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; do
n=$((n+1))
tag="$base.$n"
done
echo "tag=$tag" >> "$GITHUB_OUTPUT"
- name: Checksum
if: steps.proceed.outputs.go == 'true'
id: artifact
run: |
set -euo pipefail
aar_name="EverywhereCore-${{ steps.tag.outputs.tag }}.aar"
mv EverywhereCore.aar "$aar_name"
sha256sum "$aar_name" > "$aar_name.sha256"
echo "aar=$aar_name" >> "$GITHUB_OUTPUT"
- name: Commit + tag + push
if: steps.proceed.outputs.go == 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add go/go.mod go/go.sum
git commit -m "release ${{ steps.tag.outputs.tag }}
mihomo: ${{ steps.versions.outputs.mihomo_current }} → ${{ steps.versions.outputs.mihomo_latest }}
sing-box: ${{ steps.versions.outputs.singbox_current }} → ${{ steps.versions.outputs.singbox_latest }}
xray-core: ${{ steps.versions.outputs.xray_current }} → ${{ steps.versions.outputs.xray_latest }}"
git tag -a "${{ steps.tag.outputs.tag }}" -m "${{ steps.tag.outputs.tag }}"
# Tag + main in a single push. HEAD is detached at the
# checked-out SHA; HEAD:main pushes it to the main branch.
git push origin "HEAD:${{ github.event.repository.default_branch }}" "${{ steps.tag.outputs.tag }}"
- name: Create GitHub Release
if: steps.proceed.outputs.go == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
notes=$(cat <<EOF
### Upstream bumps
- **mihomo:** \`${{ steps.versions.outputs.mihomo_current }}\` → \`${{ steps.versions.outputs.mihomo_latest }}\`
- **sing-box:** \`${{ steps.versions.outputs.singbox_current }}\` → \`${{ steps.versions.outputs.singbox_latest }}\`
- **xray-core:** \`${{ steps.versions.outputs.xray_current }}\` → \`${{ steps.versions.outputs.xray_latest }}\`
### Consume
Download \`${{ steps.artifact.outputs.aar }}\`, drop it into
\`app/libs/\`, and add \`implementation(files("libs/${{ steps.artifact.outputs.aar }}"))\`.
EOF
)
gh release create "${{ steps.tag.outputs.tag }}" \
"${{ steps.artifact.outputs.aar }}" \
"${{ steps.artifact.outputs.aar }}.sha256" \
--title "${{ steps.tag.outputs.tag }}" \
--notes "$notes"