mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-13 15:56:14 -08:00
Share a named TUN across instances while keeping unnamed devices dedicated. Track member addresses, routes, and MTU, and reject overlapping destinations except the common Magic DNS host route. Dispatch packets between the physical TUN and member tunnels. Keep IPv4 and ordinary IPv6 source translation for mobile wrong-source traffic. Update platform configuration and runtime host integration, with unit and network namespace coverage for routing and lifecycle. Use smoltcp for packet parsing and checksum updates. Keep core CI matrix jobs independent. Reserve native inbound flows before source translations allocate their wire tuples. Keep active reservations under table pressure and leave forwarded subnet traffic outside the local flow table. Complete interface claim updates on the shared runtime and retain member registrations until the transaction commits. Keep address, route, and MTU cleanup ordered after cancelled callers, including device initialization. Project mobile VPN addresses, routes, and DNS from admitted native members. Prefer unchanged attached claims and report conflicting members individually. Apply each member's complete initial claims before opening the shared fd, so conflicts leave healthy members usable. Cover inbound IPv4/IPv6 collisions, cancellation during native changes and dispatcher acknowledgments, admission priority, and atomic mobile claim rejection with regression tests. Use scoped TestNetwork fixtures for shared TUN bridge setup and cleanup. Resolve public IPv6 namespace aliases through the lab's actual names so the new tests remain isolated under parallel upstream test runs.
442 lines
15 KiB
YAML
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: false
|
|
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
|