Compare commits

..

No commits in common. "c3aba3fc79564dd57b95d03e7cd1017e8b14f7b0" and "762a400915c2f634bdbcca36d617bea8cca32149" have entirely different histories.

12 changed files with 691 additions and 131 deletions

574
.github/workflows/alpha.yml vendored Normal file
View File

@ -0,0 +1,574 @@
name: Alpha Build
on:
# 因为 alpha 不再负责频繁构建,且需要相对于 autobuild 更稳定使用环境
# 所以不再使用 workflow_dispatch 触发
# 应当通过 git tag 来触发构建
# TODO 手动控制版本号
workflow_dispatch:
# inputs:
# tag_name:
# description: "Alpha tag name (e.g. v1.2.3-alpha.1)"
# required: true
# type: string
# push:
# # 应当限制在 dev 分支上触发发布。
# branches:
# - dev
# # 应当限制 v*.*.*-alpha* 的 tag 来触发发布。
# tags:
# - "v*.*.*-alpha*"
permissions: write-all
env:
TAG_NAME: alpha
TAG_CHANNEL: Alpha
CARGO_INCREMENTAL: 0
RUST_BACKTRACE: short
HUSKY: 0
concurrency:
group: '${{ github.workflow }} - ${{ github.head_ref || github.ref }}'
jobs:
check_alpha_tag:
name: Check Alpha Tag package.json Version Consistency
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Check tag and package.json version
id: check_tag
run: |
TAG_REF="${GITHUB_REF##*/}"
echo "Current tag: $TAG_REF"
if [[ ! "$TAG_REF" =~ -alpha ]]; then
echo "Current tag is not an alpha tag."
exit 1
fi
PKG_VERSION=$(jq -r .version package.json)
echo "package.json version: $PKG_VERSION"
if [[ "$PKG_VERSION" != *alpha* ]]; then
echo "package.json version is not an alpha version."
exit 1
fi
if [[ "$TAG_REF" != "v$PKG_VERSION" ]]; then
echo "Tag ($TAG_REF) does not match package.json version (v$PKG_VERSION)."
exit 1
fi
echo "Alpha tag and package.json version are consistent."
delete_old_assets:
name: Delete Old Alpha Release Assets and Tags
needs: check_alpha_tag
runs-on: ubuntu-latest
steps:
- name: Delete Old Alpha Tags Except Latest
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const tagPattern = /-alpha.*/; // 匹配带有 -alpha 的 tag
const owner = context.repo.owner;
const repo = context.repo.repo;
try {
// 获取所有 tag
const { data: tags } = await github.rest.repos.listTags({
owner,
repo,
per_page: 100 // 调整 per_page 以获取更多 tag
});
// 过滤出包含 -alpha 的 tag
const alphaTags = (await Promise.all(
tags
.filter(tag => tagPattern.test(tag.name))
.map(async tag => {
// 获取每个 tag 的 commit 信息以获得日期
const { data: commit } = await github.rest.repos.getCommit({
owner,
repo,
ref: tag.commit.sha
});
return {
...tag,
commitDate: commit.committer && commit.committer.date ? commit.committer.date : commit.commit.author.date
};
})
)).sort((a, b) => {
// 按 commit 日期降序排序(最新的在前面)
return new Date(b.commitDate) - new Date(a.commitDate);
});
console.log(`Found ${alphaTags.length} alpha tags`);
if (alphaTags.length === 0) {
console.log('No alpha tags found');
return;
}
// 保留最新的 tag
const latestTag = alphaTags[0];
console.log(`Keeping latest alpha tag: ${latestTag.name}`);
// 处理其他旧的 alpha tag
for (const tag of alphaTags.slice(1)) {
console.log(`Processing tag: ${tag.name}`);
// 获取与 tag 关联的 release
try {
const { data: release } = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: tag.name
});
// 删除 release 下的所有资产
if (release.assets && release.assets.length > 0) {
console.log(`Deleting ${release.assets.length} assets for release ${tag.name}`);
for (const asset of release.assets) {
console.log(`Deleting asset: ${asset.name} (${asset.id})`);
await github.rest.repos.deleteReleaseAsset({
owner,
repo,
asset_id: asset.id
});
}
}
// 删除 release
console.log(`Deleting release for tag: ${tag.name}`);
await github.rest.repos.deleteRelease({
owner,
repo,
release_id: release.id
});
// 删除 tag
console.log(`Deleting tag: ${tag.name}`);
await github.rest.git.deleteRef({
owner,
repo,
ref: `tags/${tag.name}`
});
} catch (error) {
if (error.status === 404) {
console.log(`No release found for tag ${tag.name}, deleting tag directly`);
await github.rest.git.deleteRef({
owner,
repo,
ref: `tags/${tag.name}`
});
} else {
console.error(`Error processing tag ${tag.name}:`, error);
throw error;
}
}
}
console.log('Old alpha tags and releases deleted successfully');
} catch (error) {
console.error('Error:', error);
throw error;
}
update_tag:
name: Update tag
runs-on: ubuntu-latest
needs: delete_old_assets
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Fetch UPDATE logs
id: fetch_update_logs
run: |
if [ -f "Changelog.md" ]; then
UPDATE_LOGS=$(awk '/^## v/{if(flag) exit; flag=1} flag' Changelog.md)
if [ -n "$UPDATE_LOGS" ]; then
echo "Found update logs"
echo "UPDATE_LOGS<<EOF" >> $GITHUB_ENV
echo "$UPDATE_LOGS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "No update sections found in Changelog.md"
fi
else
echo "Changelog.md file not found"
fi
shell: bash
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- run: |
if [ -z "$UPDATE_LOGS" ]; then
echo "No update logs found, using default message"
UPDATE_LOGS="More new features are now supported. Check for detailed changelog soon."
else
echo "Using found update logs"
fi
cat > release.txt << EOF
$UPDATE_LOGS
## 我应该下载哪个版本?
### MacOS
- MacOS intel芯片: x64.dmg
- MacOS apple M芯片: aarch64.dmg
### Linux
- Linux 64位: amd64.deb/amd64.rpm
- Linux arm64 architecture: arm64.deb/aarch64.rpm
- Linux armv7架构: armhf.deb/armhfp.rpm
### Windows (不再支持Win7)
#### 正常版本(推荐)
- 64位: x64-setup.exe
- arm64架构: arm64-setup.exe
#### 便携版问题很多不再提供
#### 内置Webview2版(体积较大仅在企业版系统或无法安装webview2时使用)
- 64位: x64_fixed_webview2-setup.exe
- arm64架构: arm64_fixed_webview2-setup.exe
### FAQ
- [常见问题](https://clash-verge-rev.github.io/faq/windows.html)
### 稳定机场VPN推荐
- [狗狗加速](https://verge.dginv.click/#/register?code=oaxsAGo6)
Created at ${{ env.BUILDTIME }}.
EOF
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: 'Clash Verge Rev ${{ env.TAG_CHANNEL }}'
body_path: release.txt
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
generate_release_notes: true
alpha-x86-windows-macos-linux:
name: Alpha x86 Windows, MacOS and Linux
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: windows-latest
target: aarch64-pc-windows-msvc
- os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
- name: Install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
- name: Install x86 OpenSSL (macOS only)
if: matrix.target == 'x86_64-apple-darwin'
run: |
arch -x86_64 brew install openssl@3
echo "OPENSSL_DIR=$(brew --prefix openssl@3)" >> $GITHUB_ENV
echo "OPENSSL_INCLUDE_DIR=$(brew --prefix openssl@3)/include" >> $GITHUB_ENV
echo "OPENSSL_LIB_DIR=$(brew --prefix openssl@3)/lib" >> $GITHUB_ENV
echo "PKG_CONFIG_PATH=$(brew --prefix openssl@3)/lib/pkgconfig" >> $GITHUB_ENV
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: '24.14.1'
- uses: pnpm/action-setup@v5
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
# - name: Release ${{ env.TAG_CHANNEL }} Version
# run: pnpm release-version ${{ env.TAG_NAME }}
- name: Tauri build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: '--max_old_space_size=4096'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
tagName: ${{ env.TAG_NAME }}
releaseName: 'Clash Verge Rev ${{ env.TAG_CHANNEL }}'
releaseBody: 'More new features are now supported.'
releaseDraft: false
prerelease: true
tauriScript: pnpm
args: --target ${{ matrix.target }}
alpha-arm-linux:
name: Alpha ARM Linux
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-22.04
target: aarch64-unknown-linux-gnu
arch: arm64
- os: ubuntu-22.04
target: armv7-unknown-linux-gnueabihf
arch: armhf
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Install Rust Stable
uses: dtolnay/rust-toolchain@stable
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: '24.14.1'
- name: Install pnpm
uses: pnpm/action-setup@v5
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
# - name: Release ${{ env.TAG_CHANNEL }} Version
# run: pnpm release-version ${{ env.TAG_NAME }}
- name: Setup for linux
run: |
sudo ls -lR /etc/apt/
cat > /tmp/sources.list << EOF
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-security main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-updates main multiverse universe restricted
deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu jammy-backports main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-security main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-updates main multiverse universe restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports jammy-backports main multiverse universe restricted
EOF
sudo mv /etc/apt/sources.list /etc/apt/sources.list.default
sudo mv /tmp/sources.list /etc/apt/sources.list
sudo dpkg --add-architecture ${{ matrix.arch }}
sudo apt-get update -y
sudo apt-get -f install -y
sudo apt-get install -y \
linux-libc-dev:${{ matrix.arch }} \
libc6-dev:${{ matrix.arch }}
sudo apt-get install -y \
libxslt1.1:${{ matrix.arch }} \
libwebkit2gtk-4.1-dev:${{ matrix.arch }} \
libayatana-appindicator3-dev:${{ matrix.arch }} \
libssl-dev:${{ matrix.arch }} \
patchelf:${{ matrix.arch }} \
librsvg2-dev:${{ matrix.arch }}
- name: Install aarch64 tools
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt install -y \
gcc-aarch64-linux-gnu \
g++-aarch64-linux-gnu
- name: Install armv7 tools
if: matrix.target == 'armv7-unknown-linux-gnueabihf'
run: |
sudo apt install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf
- name: Build for Linux
run: |
export PKG_CONFIG_ALLOW_CROSS=1
if [ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]; then
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu/
elif [ "${{ matrix.target }}" == "armv7-unknown-linux-gnueabihf" ]; then
export PKG_CONFIG_PATH=/usr/lib/arm-linux-gnueabihf/pkgconfig/:$PKG_CONFIG_PATH
export PKG_CONFIG_SYSROOT_DIR=/usr/arm-linux-gnueabihf/
fi
pnpm build --target ${{ matrix.target }}
env:
NODE_OPTIONS: '--max_old_space_size=4096'
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Get Version
run: |
sudo apt-get update
sudo apt-get install jq
echo "VERSION=$(cat package.json | jq '.version' | tr -d '"')" >> $GITHUB_ENV
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: 'Clash Verge Rev ${{ env.TAG_CHANNEL }}'
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: |
src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
src-tauri/target/${{ matrix.target }}/release/bundle/rpm/*.rpm
alpha-x86-arm-windows_webview2:
name: Alpha x86 and ARM Windows with WebView2
needs: update_tag
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
arch: x64
- os: windows-latest
target: aarch64-pc-windows-msvc
arch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri
save-if: false
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: '24.14.1'
- uses: pnpm/action-setup@v5
name: Install pnpm
with:
run_install: false
- name: Pnpm install and check
run: |
pnpm i
pnpm run prebuild ${{ matrix.target }}
# - name: Release ${{ env.TAG_CHANNEL }} Version
# run: pnpm release-version ${{ env.TAG_NAME }}
- name: Download WebView2 Runtime
run: |
invoke-webrequest -uri https://github.com/westinyang/WebView2RuntimeArchive/releases/download/133.0.3065.92/Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -outfile Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab
Expand .\Microsoft.WebView2.FixedVersionRuntime.133.0.3065.92.${{ matrix.arch }}.cab -F:* ./src-tauri
Remove-Item .\src-tauri\tauri.windows.conf.json
Rename-Item .\src-tauri\webview2.${{ matrix.arch }}.json tauri.windows.conf.json
- name: Tauri build
id: build
uses: tauri-apps/tauri-action@v0
env:
NODE_OPTIONS: '--max_old_space_size=4096'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with:
tauriScript: pnpm
args: --target ${{ matrix.target }}
- name: Rename
run: |
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe$", "_fixed_webview2-setup.exe"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*.nsis.zip"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.nsis\.zip$", "_fixed_webview2-setup.nsis.zip"
Rename-Item $file.FullName $newName
}
$files = Get-ChildItem ".\src-tauri\target\${{ matrix.target }}\release\bundle\nsis\*-setup.exe.sig"
foreach ($file in $files) {
$newName = $file.Name -replace "-setup\.exe\.sig$", "_fixed_webview2-setup.exe.sig"
Rename-Item $file.FullName $newName
}
- name: Upload Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ env.TAG_NAME }}
name: 'Clash Verge Rev ${{ env.TAG_CHANNEL }}'
prerelease: true
token: ${{ secrets.GITHUB_TOKEN }}
files: src-tauri/target/${{ matrix.target }}/release/bundle/nsis/*setup*
- name: Portable Bundle
run: pnpm portable-fixed-webview2 ${{ matrix.target }} --${{ env.TAG_NAME }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -65,7 +65,7 @@ jobs:
title: ${{ steps.sanitized.outputs.title }}
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@0048fdad270986610dd71c966d15f7abd7de037a # v0.64.2
uses: github/gh-aw-actions/setup@ef6d19b6ddd5d24eb2787355d2afcd70bd8392bf # v0.64.1
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Generate agentic run info
@ -277,7 +277,7 @@ jobs:
output_types: ${{ steps.collect_output.outputs.output_types }}
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@0048fdad270986610dd71c966d15f7abd7de037a # v0.64.2
uses: github/gh-aw-actions/setup@ef6d19b6ddd5d24eb2787355d2afcd70bd8392bf # v0.64.1
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Set runtime paths
@ -800,7 +800,7 @@ jobs:
total_count: ${{ steps.missing_tool.outputs.total_count }}
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@0048fdad270986610dd71c966d15f7abd7de037a # v0.64.2
uses: github/gh-aw-actions/setup@ef6d19b6ddd5d24eb2787355d2afcd70bd8392bf # v0.64.1
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Download agent output artifact
@ -895,7 +895,7 @@ jobs:
detection_success: ${{ steps.detection_conclusion.outputs.success }}
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@0048fdad270986610dd71c966d15f7abd7de037a # v0.64.2
uses: github/gh-aw-actions/setup@ef6d19b6ddd5d24eb2787355d2afcd70bd8392bf # v0.64.1
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Download agent output artifact
@ -1056,7 +1056,7 @@ jobs:
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
steps:
- name: Setup Scripts
uses: github/gh-aw-actions/setup@0048fdad270986610dd71c966d15f7abd7de037a # v0.64.2
uses: github/gh-aw-actions/setup@ef6d19b6ddd5d24eb2787355d2afcd70bd8392bf # v0.64.1
with:
destination: ${{ runner.temp }}/gh-aw/actions
- name: Download agent output artifact

65
Cargo.lock generated
View File

@ -43,6 +43,19 @@ dependencies = [
"subtle",
]
[[package]]
name = "ahash"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom 0.3.4",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.4"
@ -1311,7 +1324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
"windows-sys 0.48.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3483,7 +3496,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2 0.5.10",
"socket2 0.6.3",
"system-configuration",
"tokio",
"tower-service",
@ -3503,7 +3516,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.61.2",
"windows-core 0.62.2",
]
[[package]]
@ -3770,15 +3783,13 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "iptools"
version = "0.4.1"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d423b27a03e1931bcf0a52bc6c554f0b48573f2f51688c92ac6cddf08494943c"
checksum = "9bab2ab6edf9330906c4da149dddc19e850b14c682865d81fb21962bbfc2e0ae"
dependencies = [
"js-sys",
"lazy-regex",
"ahash",
"once_cell",
"tinyvec",
"wasm-bindgen",
"regex",
]
[[package]]
@ -4023,29 +4034,6 @@ dependencies = [
"selectors 0.24.0",
]
[[package]]
name = "lazy-regex"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496"
dependencies = [
"lazy-regex-proc_macros",
"once_cell",
"regex",
]
[[package]]
name = "lazy-regex-proc_macros"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358"
dependencies = [
"proc-macro2",
"quote",
"regex",
"syn 2.0.117",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -4891,7 +4879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.45.0",
"windows-sys 0.61.2",
]
[[package]]
@ -5685,7 +5673,7 @@ dependencies = [
"quinn-udp",
"rustc-hash",
"rustls",
"socket2 0.5.10",
"socket2 0.6.3",
"thiserror 2.0.18",
"tokio",
"tracing",
@ -5723,7 +5711,7 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2 0.5.10",
"socket2 0.6.3",
"tracing",
"windows-sys 0.60.2",
]
@ -7274,12 +7262,11 @@ dependencies = [
[[package]]
name = "sysproxy"
version = "0.5.3"
source = "git+https://github.com/clash-verge-rev/sysproxy-rs?branch=0.5.3#7df1fac9454d8fc900167e2cb55b723ceb4d1194"
version = "0.4.5"
source = "git+https://github.com/clash-verge-rev/sysproxy-rs?branch=0.4.5#6aa9ecb1c6bb9779b9dc8e4adc68780fc1d5edb6"
dependencies = [
"iptools",
"log",
"system-configuration",
"thiserror 2.0.18",
"tokio",
"url",
@ -9423,7 +9410,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.48.0",
"windows-sys 0.61.2",
]
[[package]]

View File

@ -6,11 +6,7 @@
### 🐞 修复问题
- 修复系统代理关闭后在 PAC 模式下未完全关闭
- 修复 macOS 开关代理时可能的卡死
- 修复修改定时自动更新后记时未及时刷新
### ✨ 新增功能
### 🚀 优化改进
- 优化 macOS 读取系统代理性能

View File

@ -71,7 +71,7 @@ reqwest = { version = "0.13.2", features = [
"form",
] }
regex = "1.12.3"
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "0.5.3", features = [
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", branch = "0.4.5", features = [
"guard",
] }
network-interface = { version = "2.0.5", features = ["serde"] }

View File

@ -185,7 +185,6 @@ pub async fn delete_profile(index: String) -> CmdResult {
}
}
}
Timer::global().refresh().await.stringify_err()?;
Ok(())
}

View File

@ -15,10 +15,9 @@ use std::{
time::Duration,
};
use sysproxy::{Autoproxy, GuardMonitor, GuardType, Sysproxy};
use tokio::sync::Mutex as TokioMutex;
pub struct Sysopt {
update_lock: TokioMutex<()>,
update_sysproxy: AtomicBool,
reset_sysproxy: AtomicBool,
inner_proxy: Arc<RwLock<(Sysproxy, Autoproxy)>>,
guard: Arc<RwLock<GuardMonitor>>,
@ -27,7 +26,7 @@ pub struct Sysopt {
impl Default for Sysopt {
fn default() -> Self {
Self {
update_lock: TokioMutex::new(()),
update_sysproxy: AtomicBool::new(false),
reset_sysproxy: AtomicBool::new(false),
inner_proxy: Arc::new(RwLock::new((Sysproxy::default(), Autoproxy::default()))),
guard: Arc::new(RwLock::new(GuardMonitor::new(GuardType::None, Duration::from_secs(30)))),
@ -108,70 +107,95 @@ impl Sysopt {
/// init the sysproxy
pub async fn update_sysproxy(&self) -> Result<()> {
let _lock = self.update_lock.lock().await;
if self.update_sysproxy.load(Ordering::Acquire) {
logging!(info, Type::Core, "Sysproxy update is already in progress.");
return Ok(());
}
if self
.update_sysproxy
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
logging!(info, Type::Core, "Sysproxy update is already in progress.");
return Ok(());
}
defer! {
logging!(info, Type::Core, "Sysproxy update completed.");
self.update_sysproxy.store(false, Ordering::Release);
}
let verge = Config::verge().await.latest_arc();
let port = match verge.verge_mixed_port {
Some(port) => port,
None => Config::clash().await.latest_arc().get_mixed_port(),
let port = {
let verge_port = verge.verge_mixed_port;
match verge_port {
Some(port) => port,
None => Config::clash().await.latest_arc().get_mixed_port(),
}
};
let pac_port = IVerge::get_singleton_port();
let (sys_enable, pac_enable, proxy_host, proxy_guard) = (
verge.enable_system_proxy.unwrap_or_default(),
verge.proxy_auto_config.unwrap_or_default(),
verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")),
verge.enable_proxy_guard.unwrap_or_default(),
);
let (sys_enable, pac_enable, proxy_host, proxy_guard) = {
(
verge.enable_system_proxy.unwrap_or_default(),
verge.proxy_auto_config.unwrap_or_default(),
verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")),
verge.enable_proxy_guard.unwrap_or_default(),
)
};
// 先 await, 避免持有锁导致的 Send 问题
let bypass = get_bypass().await;
let (sys, auto, guard_type) = {
let (sys, auto) = &mut *self.inner_proxy.write();
sys.host = proxy_host.clone().into();
sys.port = port;
sys.bypass = bypass.into();
auto.url = format!("http://{proxy_host}:{pac_port}/commands/pac");
let (sys, auto) = &mut *self.inner_proxy.write();
sys.enable = false;
sys.host = proxy_host.clone().into();
sys.port = port;
sys.bypass = bypass.into();
// `enable_system_proxy` is the master switch.
// When disabled, force clear both global proxy and PAC at OS level.
let guard_type = if !sys_enable {
sys.enable = false;
auto.enable = false;
GuardType::None
} else if pac_enable {
sys.enable = false;
auto.enable = true;
if proxy_guard {
GuardType::Autoproxy(auto.clone())
} else {
GuardType::None
}
} else {
sys.enable = true;
auto.enable = false;
if proxy_guard {
GuardType::Sysproxy(sys.clone())
} else {
GuardType::None
}
};
auto.enable = false;
auto.url = format!("http://{proxy_host}:{pac_port}/commands/pac");
(sys.clone(), auto.clone(), guard_type)
};
self.access_guard().write().set_guard_type(GuardType::None);
self.access_guard().write().set_guard_type(guard_type);
tokio::task::spawn_blocking(move || -> Result<()> {
// `enable_system_proxy` is the master switch.
// When disabled, force clear both global proxy and PAC at OS level.
if !sys_enable {
sys.set_system_proxy()?;
auto.set_auto_proxy()?;
Ok(())
})
.await??;
return Ok(());
}
if pac_enable {
sys.enable = false;
auto.enable = true;
sys.set_system_proxy()?;
auto.set_auto_proxy()?;
if proxy_guard {
self.access_guard()
.write()
.set_guard_type(GuardType::Autoproxy(auto.clone()));
}
return Ok(());
}
if sys_enable {
auto.enable = false;
sys.enable = true;
auto.set_auto_proxy()?;
sys.set_system_proxy()?;
if proxy_guard {
self.access_guard()
.write()
.set_guard_type(GuardType::Sysproxy(sys.clone()));
}
return Ok(());
}
Ok(())
}
/// reset the sysproxy
#[allow(clippy::unused_async)]
pub async fn reset_sysproxy(&self) -> Result<()> {
if self
.reset_sysproxy
@ -188,19 +212,11 @@ impl Sysopt {
self.access_guard().write().set_guard_type(GuardType::None);
// 直接关闭所有代理
let (sys, auto) = {
let (sys, auto) = &mut *self.inner_proxy.write();
sys.enable = false;
auto.enable = false;
(sys.clone(), auto.clone())
};
tokio::task::spawn_blocking(move || -> Result<()> {
sys.set_system_proxy()?;
auto.set_auto_proxy()?;
Ok(())
})
.await??;
let (sys, auto) = &mut *self.inner_proxy.write();
sys.enable = false;
sys.set_system_proxy()?;
auto.enable = false;
auto.set_auto_proxy()?;
Ok(())
}

View File

@ -126,11 +126,11 @@ impl Timer {
profiles_to_update.len()
);
let timer_map = self.timer_map.read();
let delay_timer = self.delay_timer.write();
for uid in profiles_to_update {
if let Some(task) = timer_map.get(&uid) {
logging!(info, Type::Timer, "立即执行任务: uid={}", uid);
let delay_timer = self.delay_timer.write();
if let Err(e) = delay_timer.advance_task(task.task_id) {
logging!(warn, Type::Timer, "Failed to advance task {}: {}", uid, e);
}

View File

@ -7,7 +7,7 @@ fn deep_merge(a: &mut Value, b: Value) {
match (a, b) {
(&mut Value::Mapping(ref mut a), Value::Mapping(b)) => {
for (k, v) in b {
deep_merge(a.entry(k).or_insert(Value::Null), v);
deep_merge(a.entry(k.clone()).or_insert(Value::Null), v);
}
}
(a, b) => *a = b,

View File

@ -157,12 +157,6 @@ export const SystemInfoCard = () => {
const onCheckUpdate = useLockFn(async () => {
try {
const info = await triggerCheckUpdate()
const now = Date.now()
localStorage.setItem('last_check_update', now.toString())
dispatchSystemState({
type: 'set-last-check-update',
payload: new Date(now).toLocaleString(),
})
if (!info?.available) {
showNotice.success(
'settings.components.verge.advanced.notifications.latestVersion',

View File

@ -578,8 +578,6 @@ export const ProfileItem = (props: Props) => {
const customEvent = event as CustomEvent<{ uid?: string }>
if (customEvent.detail?.uid === itemData.uid) {
setLoadingCache((cache) => ({ ...cache, [itemData.uid]: false }))
// 刷新 profile 数据以获取最新的 updated 时间戳
mutate('getProfiles')
// 更新完成后刷新显示
if (showNextUpdate) {
fetchNextUpdateTime()

View File

@ -67,14 +67,10 @@ const SwitchRow = ({
const handleChange = (_: React.ChangeEvent, value: boolean) => {
pendingRef.current = true
setChecked(value)
onToggle(value)
.catch((err: any) => {
setChecked(active)
onError?.(err)
})
.finally(() => {
pendingRef.current = false
})
onToggle(value).catch((err: any) => {
pendingRef.current = false
onError?.(err)
})
}
return (