Files
EasyTier/.github/workflows/core.yml
T
KKRainbow f8a45ccc20 ci: use lightweight PR checks and conditional nightly builds (#2677)
* ci: use lightweight PR checks and conditional nightly builds

Check three Core platforms, Linux GUI, and Android ARM64 on PRs and
ordinary branch pushes. Keep formatting, clippy, WASI, and all Linux
tests; reserve the per-feature sweep for full builds.

Run complete platform matrices on releases/** pushes and manual
dispatch. At 03:17 Asia/Shanghai, check existing main and develop
branches and dispatch only workflows whose last successful full build
used a different commit, retrying failed components on the next night.

Cache compilation outputs by target and build mode. Build only the GUI
frontend dependencies and reuse the dashboard artifact for Core builds.
Separate full-build concurrency so ordinary pushes cannot cancel them.

Keep the required result checks and propagate failures and
cancellations. Validate successful full-build origins and matching Core,
GUI, and Mobile commits before preparing release assets.

Validation: actionlint 1.7.12, 10 Node tests, read-only GitHub nightly
dry run, GUI frontend build, Linux GUI check, Android ARM64 check, and
Linux musl Core/Web checks including embedded dashboard assets.

* docs: describe CI behavior in English and Chinese

Document PR checks, nightly dispatch, manual builds, release validation,
artifacts, and independent workflows. Keep both versions aligned with
current workflow definitions.

* ci: decouple PR checks from Web dashboard builds

Run PR dashboard builds only when frontend sources, Core configuration
sources, protobuf schemas, or build configuration change. Keep embedded
Web builds in full mode and let ordinary Rust checks start without
frontend artifacts.

Upload dashboard artifacts only for full builds. Skip frontend setup for
ordinary lint runs and remove GUI/frontend setup from test archives.
Update both CI documents to match the new behavior.

Validation: actionlint; frontend dependency and workflow routing cases;
Linux Core cargo check and workspace clippy with frontend outputs
absent.
2026-10-12 00:13:12 +08:00

442 lines
15 KiB
YAML

name: EasyTier Core
on:
workflow_dispatch:
inputs:
sign_windows:
description: Submit Windows artifacts for SignPath test signing
type: boolean
default: true
push:
branches: [ "develop", "main", "releases/**" ]
pull_request:
branches: [ "develop", "main" ]
types: [ opened, synchronize, reopened, ready_for_review ]
concurrency:
# A new PR or branch push must not cancel a full build.
group: ${{ github.workflow }}-${{ (github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'releases/')) && 'full' || 'check' }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
defaults:
run:
# necessary for windows
shell: bash
jobs:
pre_job:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
env:
RELEVANT_PATHS: >-
["Cargo.toml", "Cargo.lock", "rust-toolchain.toml", ".cargo/**",
"pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "easytier/**",
"easytier-core/**", "easytier-proto/**", "easytier-rpc-build/**",
"easytier-web/**", "easytier-contrib/easytier-magisk/**",
".github/workflows/core.yml", ".github/actions/**"]
FRONTEND_PATHS: >-
["easytier-web/frontend/**", "easytier-web/frontend-lib/**",
"easytier-web/config-generator/**", "easytier-proto/proto/**",
"easytier-core/src/config/**", "easytier-core/src/lib.rs",
"pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json",
"Cargo.toml", "Cargo.lock", "rust-toolchain.toml", ".cargo/**",
"easytier-core/Cargo.toml", "easytier-proto/Cargo.toml",
".github/workflows/core.yml", ".github/actions/prepare-pnpm/**"]
outputs:
full_build: ${{ github.event_name == 'workflow_dispatch' || startsWith(github.ref_name, 'releases/') }}
frontend_changed: ${{ steps.path_filter.outputs.frontend }}
# Release builds must run even if they only change release metadata.
should_skip: >-
${{
(
steps.skip_check.outputs.should_skip == 'true' ||
(
github.event_name == 'pull_request' &&
steps.path_filter.outputs.relevant != 'true'
)
) &&
github.event_name != 'workflow_dispatch' &&
!startsWith(github.ref_name, 'releases/')
}}
steps:
- id: skip_check
if: github.event_name != 'workflow_dispatch' && !startsWith(github.ref_name, 'releases/')
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: 'same_content_newer'
skip_after_successful_duplicate: 'true'
paths: ${{ env.RELEVANT_PATHS }}
- id: path_filter
if: >-
github.event_name == 'pull_request' &&
steps.skip_check.outputs.should_skip != 'true'
uses: dorny/paths-filter@v4
with:
filters: |
relevant: ${{ env.RELEVANT_PATHS }}
frontend: ${{ env.FRONTEND_PATHS }}
check:
name: Check (${{ matrix.TARGET }})
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true' && needs.pre_job.outputs.full_build != 'true'
strategy:
fail-fast: true
matrix:
include:
- TARGET: x86_64-unknown-linux-musl
OS: ubuntu-24.04
FEATURES: jemalloc
- TARGET: aarch64-apple-darwin
OS: macos-latest
FEATURES: jemalloc
- TARGET: x86_64-pc-windows-msvc
OS: windows-latest
FEATURES: mimalloc
runs-on: ${{ matrix.OS }}
steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/prepare-build
with:
target: ${{ matrix.TARGET }}
gui: false
pnpm: false
token: ${{ github.token }}
- uses: Swatinem/rust-cache@v2
with:
shared-key: core-check-${{ matrix.TARGET }}
save-if: ${{ github.event_name != 'pull_request' }}
- name: Check Core and Web
run: cargo check --locked --all-targets --target ${{ matrix.TARGET }} --features ${{ matrix.FEATURES }}
build_web:
runs-on: ubuntu-latest
needs: pre_job
if: >-
needs.pre_job.outputs.should_skip != 'true' &&
(
needs.pre_job.outputs.full_build == 'true' ||
github.event_name != 'pull_request' ||
needs.pre_job.outputs.frontend_changed == 'true'
)
steps:
- uses: actions/checkout@v5
- name: Setup Frontend Environment
uses: ./.github/actions/prepare-pnpm
with:
token: ${{ github.token }}
build-filter: './easytier-web/*'
- name: Archive artifact
if: needs.pre_job.outputs.full_build == 'true'
uses: actions/upload-artifact@v5
with:
name: easytier-web-dashboard
path: |
easytier-web/frontend/dist/*
build:
strategy:
fail-fast: true
matrix:
include:
- TARGET: x86_64-unknown-linux-musl
OS: ubuntu-24.04
ARTIFACT_NAME: linux-x86_64
- TARGET: aarch64-unknown-linux-musl
OS: ubuntu-24.04-arm
ARTIFACT_NAME: linux-aarch64
- TARGET: riscv64gc-unknown-linux-musl
OS: ubuntu-24.04
ARTIFACT_NAME: linux-riscv64
- TARGET: loongarch64-unknown-linux-musl
OS: ubuntu-24.04
ARTIFACT_NAME: linux-loongarch64
- TARGET: armv7-unknown-linux-musleabihf # raspberry pi 2-3-4, not tested
OS: ubuntu-24.04
ARTIFACT_NAME: linux-armv7hf
- TARGET: armv7-unknown-linux-musleabi # raspberry pi 2-3-4, not tested
OS: ubuntu-24.04
ARTIFACT_NAME: linux-armv7
- TARGET: arm-unknown-linux-musleabihf # raspberry pi 0-1, not tested
OS: ubuntu-24.04
ARTIFACT_NAME: linux-armhf
- TARGET: arm-unknown-linux-musleabi # raspberry pi 0-1, not tested
OS: ubuntu-24.04
ARTIFACT_NAME: linux-arm
- TARGET: mips-unknown-linux-musl
OS: ubuntu-24.04
ARTIFACT_NAME: linux-mips
- TARGET: mipsel-unknown-linux-musl
OS: ubuntu-24.04
ARTIFACT_NAME: linux-mipsel
- TARGET: x86_64-unknown-freebsd
OS: ubuntu-24.04
ARTIFACT_NAME: freebsd-13.2-x86_64
BSD_VERSION: 13.2
- TARGET: x86_64-apple-darwin
OS: macos-latest
ARTIFACT_NAME: macos-x86_64
- TARGET: aarch64-apple-darwin
OS: macos-latest
ARTIFACT_NAME: macos-aarch64
- TARGET: x86_64-pc-windows-msvc
OS: windows-latest
ARTIFACT_NAME: windows-x86_64
- TARGET: i686-pc-windows-msvc
OS: windows-latest
ARTIFACT_NAME: windows-i686
- TARGET: aarch64-pc-windows-msvc
OS: windows-11-arm
ARTIFACT_NAME: windows-arm64
runs-on: ${{ matrix.OS }}
env:
NAME: easytier
TARGET: ${{ matrix.TARGET }}
OS: ${{ matrix.OS }}
OSS_BUCKET: ${{ secrets.ALIYUN_OSS_BUCKET }}
needs:
- pre_job
- build_web
if: needs.pre_job.outputs.should_skip != 'true' && needs.pre_job.outputs.full_build == 'true'
steps:
- uses: actions/checkout@v5
- name: Set current ref as env variable
run: |
echo "GIT_DESC=$(git log -1 --format=%cd.%h --date=format:%Y-%m-%d_%H:%M:%S)" >> $GITHUB_ENV
- name: Download web artifact
uses: actions/download-artifact@v4
with:
name: easytier-web-dashboard
path: easytier-web/frontend/dist/
- name: Prepare build environment
uses: ./.github/actions/prepare-build
with:
target: ${{ matrix.TARGET }}
gui: false
pnpm: false
token: ${{ secrets.GITHUB_TOKEN }}
- uses: Swatinem/rust-cache@v2
with:
# The prefix cache key, this can be changed to start a new cache manually.
# default: "v0-rust"
prefix-key: ""
shared-key: core-release-${{ matrix.TARGET }}
cache-targets: "true"
- uses: mlugg/setup-zig@v2
if: ${{ contains(matrix.OS, 'ubuntu') }}
with:
version: 0.16.0
use-cache: true
- uses: taiki-e/install-action@v2
if: ${{ contains(matrix.OS, 'ubuntu') }}
with:
# v0.23.3 emits -mcpu=generic+v6+strict_align for
# arm-unknown-linux-musleabi, which zig 0.16.0 rejects;
# unpin only together with a zig bump.
tool: cargo-zigbuild@0.23.2
- name: Build
if: ${{ !contains(matrix.TARGET, 'mips') }}
run: |
if [[ "$TARGET" == *windows* ]]; then
SUFFIX=.exe
else
SUFFIX=""
fi
if [[ "$TARGET" =~ (x86_64-unknown-linux-musl|aarch64-unknown-linux-musl|windows|darwin) ]]; then
BUILD=build
else
BUILD=zigbuild
fi
if [[ "$TARGET" =~ ^(riscv64|loongarch64|aarch64).*$ || "$TARGET" =~ (freebsd|windows) ]]; then
FEATURES="mimalloc"
else
FEATURES="jemalloc"
fi
cargo $BUILD --release --target $TARGET --package=easytier-web --features=embed
mv ./target/$TARGET/release/easytier-web"$SUFFIX" ./target/$TARGET/release/easytier-web-embed"$SUFFIX"
cargo $BUILD --release --target $TARGET --features=$FEATURES
- name: Build (MIPS)
if: ${{ contains(matrix.TARGET, 'mips') }}
env:
RUSTC_BOOTSTRAP: 1
run: |
cargo build -r --target $TARGET -Z build-std=std,panic_abort --package=easytier --features=jemalloc
- name: Compress
run: |
mkdir -p ./artifacts/objects/
# windows is the only OS using a different convention for executable file name
if [[ $OS =~ ^windows.*$ ]]; then
SUFFIX=.exe
case $TARGET in
x86_64*) ARCH_DIR=x86_64 ;;
i686*) ARCH_DIR=i686 ;;
aarch64*) ARCH_DIR=arm64 ;;
esac
if [[ -n "$ARCH_DIR" ]]; then
find "easytier/third_party/${ARCH_DIR}" -maxdepth 1 -type f \( -name "*.dll" -o -name "*.sys" \) -exec cp {} ./artifacts/objects/ \;
fi
fi
if [[ $GITHUB_REF_TYPE =~ ^tag$ ]]; then
TAG=$GITHUB_REF_NAME
else
TAG=$GITHUB_SHA
fi
if [[ $OS =~ ^ubuntu.*$ && ! $TARGET =~ (loongarch|freebsd) ]]; then
HOST_ARCH=$(uname -m)
case $HOST_ARCH in
x86_64) UPX_ARCH="amd64" ;;
aarch64) UPX_ARCH="arm64" ;;
*) UPX_ARCH="amd64" ;;
esac
UPX_VERSION=4.2.4
UPX_PKG="upx-${UPX_VERSION}-${UPX_ARCH}_linux"
curl -L "https://github.com/upx/upx/releases/download/v${UPX_VERSION}/${UPX_PKG}.tar.xz" -s | tar xJvf -
cp "${UPX_PKG}/upx" .
UPX_BIN=./upx
fi
for BIN in ./target/$TARGET/release/easytier-{core,cli,web,web-embed}"$SUFFIX"; do
if [[ -f "$BIN" ]]; then
if [[ -n "$UPX_BIN" ]]; then
$UPX_BIN --lzma --best "$BIN" || true
fi
mv "$BIN" ./artifacts/objects/
fi
done
mv ./artifacts/objects/* ./artifacts/
rm -rf ./artifacts/objects/
- name: Archive artifact
uses: actions/upload-artifact@v5
with:
name: easytier-${{ matrix.ARTIFACT_NAME }}
path: |
./artifacts/*
- name: Prepare SignPath test artifact
if: ${{ github.event_name == 'workflow_dispatch' && inputs.sign_windows && contains(matrix.TARGET, 'windows') }}
run: |
mkdir -p ./signpath-artifacts
cp \
./artifacts/easytier-core.exe \
./artifacts/easytier-cli.exe \
./artifacts/easytier-web.exe \
./artifacts/easytier-web-embed.exe \
./signpath-artifacts/
- name: Upload unsigned SignPath test artifact
if: ${{ github.event_name == 'workflow_dispatch' && inputs.sign_windows && contains(matrix.TARGET, 'windows') }}
id: signpath-upload
uses: actions/upload-artifact@v5
with:
name: signpath-unsigned-${{ matrix.ARTIFACT_NAME }}
path: ./signpath-artifacts/*
if-no-files-found: error
- name: Submit SignPath test-signing request
if: ${{ github.event_name == 'workflow_dispatch' && inputs.sign_windows && contains(matrix.TARGET, 'windows') }}
uses: signpath/github-action-submit-signing-request@v2
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: 368cf38d-86d5-4bfe-a1fa-82a50dce7e9f
project-slug: EasyTier
signing-policy-slug: test-signing
artifact-configuration-slug: initial
github-artifact-id: ${{ steps.signpath-upload.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: ./signpath-signed-artifacts
- name: Upload signed SignPath test artifact
if: ${{ github.event_name == 'workflow_dispatch' && inputs.sign_windows && contains(matrix.TARGET, 'windows') }}
uses: actions/upload-artifact@v5
with:
name: signpath-signed-${{ matrix.ARTIFACT_NAME }}
path: ./signpath-signed-artifacts/*
if-no-files-found: error
build_magisk:
runs-on: ubuntu-latest
needs: [ pre_job, build_web, build ]
if: needs.pre_job.result == 'success' && needs.pre_job.outputs.should_skip != 'true' && needs.pre_job.outputs.full_build == 'true' && !cancelled()
steps:
- name: Checkout Code
uses: actions/checkout@v5 # 必须先检出代码才能获取模块配置
# 下载二进制文件到独立目录
- name: Download Linux aarch64 binaries
uses: actions/download-artifact@v4
with:
name: easytier-linux-aarch64
path: ./downloaded-binaries/ # 独立目录避免冲突
- name: Download Magisk WebUI
uses: actions/download-artifact@v4
with:
name: easytier-web-dashboard
path: ./downloaded-webui/
# 将二进制文件和 WebUI 复制到 Magisk 模块目录
- name: Prepare Magisk module
run: |
module_dir=./easytier-contrib/easytier-magisk
mkdir -p "$module_dir/webroot"
cp ./downloaded-binaries/easytier-core "$module_dir/"
cp ./downloaded-binaries/easytier-cli "$module_dir/"
cp ./downloaded-binaries/easytier-web "$module_dir/"
cp -R ./downloaded-webui/config-generator/. "$module_dir/webroot/"
test -f "$module_dir/webroot/index.html"
# 上传生成的模块
- name: Upload Magisk Module
uses: actions/upload-artifact@v5
with:
name: Easytier-Magisk
path: |
./easytier-contrib/easytier-magisk
!./easytier-contrib/easytier-magisk/build.sh
!./easytier-contrib/easytier-magisk/magisk_update.json
if-no-files-found: error
core-result:
runs-on: ubuntu-latest
needs: [ pre_job, check, build_web, build, build_magisk ]
if: ${{ always() }}
steps:
- name: Mark result as failed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1