构建指南
本指南介绍如何从源码构建 Zephyr。Zephyr 是基于 Tauri v2 的 Mihomo GUI 代理客户端,前端使用原生 JavaScript(无框架,UnoCSS 样式),后端使用 Rust;仓库是 pnpm workspace + Cargo workspace 组成的 monorepo。
目录
项目结构
前端与 Rust 后端通过 Tauri IPC 通信,IPC 命令名常量集中在 packages/shared:
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TB
subgraph root["仓库根目录"]
A["package.json<br/>workspace 根脚本"]
B["pnpm-workspace.yaml<br/>apps/* + packages/*"]
C["Cargo.toml<br/>Cargo workspace + 统一 Lint"]
D["rust-toolchain.toml<br/>固定 Rust 1.96.0"]
end
subgraph desktop["apps/desktop/ - 桌面应用 (@zephyr/desktop)"]
E["src/<br/>原生 JS 前端 / 样式 / UI 模块"]
F["src-tauri/<br/>Rust 后端 (crate: Zephyr)"]
G["src-tauri/tauri.conf.json<br/>Tauri 与打包配置"]
H["src-tauri/bundled/<br/>Full 版内置资源目录"]
I["uno.config.js / eslint.config.mjs<br/>vitest.config.js"]
end
subgraph crates["crates/ - 共享 Rust 库"]
J["core/<br/>zephyr-core 跨平台业务逻辑"]
K["core-ffi/<br/>zephyr-core-ffi UniFFI 移动端绑定"]
end
subgraph packages["packages/ - 共享包"]
L["shared/<br/>IPC 命令名常量 (COMMANDS)"]
M["tokens/<br/>Design Token (Style Dictionary)"]
N["scripts/<br/>i18n 完整性检查"]
end
A --> desktop
B --> packages
C --> F
C --> crates
F --> J
L --> E
M --> E目录说明
| 目录/文件 | 说明 |
|---|---|
apps/desktop/ |
桌面应用主包 @zephyr/desktop |
apps/desktop/src/ |
原生 JavaScript 前端(index.html、main.js、api.js、ui/、utils/、modules/ 等),也是 Tauri 的 frontendDist |
apps/desktop/src-tauri/ |
Tauri v2 Rust 后端(crate 名 Zephyr,lib 名 my_clash_gui_lib) |
apps/desktop/src-tauri/tauri.conf.json |
应用标识 com.zephyr.desktop、窗口、CSP、打包目标与安装器配置 |
apps/desktop/src-tauri/tauri.linux.conf.json |
Linux 专用:打包前安装自定义 linuxdeploy-plugin-gtk.sh(AppImage 用) |
apps/desktop/src-tauri/bundled/ |
Full 版内置资源(mihomo 内核与 Geo 数据),仓库中只有 .gitkeep |
apps/desktop/src-tauri/THIRD-PARTY-LICENSES.txt |
随安装包分发的第三方许可证全文(脚本生成,见下文) |
crates/core/ |
zephyr-core:跨平台纯业务逻辑(配置合并、清洗、订阅等) |
crates/core-ffi/ |
zephyr-core-ffi:UniFFI 移动端绑定入口(cdylib / staticlib) |
packages/shared/ |
前后端共享的 IPC 命令名常量(构建时复制到 src/_shared/) |
packages/tokens/ |
Design Token 源文件与 Style Dictionary 配置 |
packages/scripts/ |
check-i18n.js i18n 完整性检查器 |
scripts/ |
许可证生成、SBOM 合并、AppImage GTK 插件、移动端预构建脚本 |
.github/workflows/ |
GitHub Actions 工作流 |
.husky/ |
本地 Git 钩子(pre-commit / pre-push) |
.devcontainer/ |
VS Code 开发容器配置 |
环境要求
通用依赖
| 工具 | 版本 | 来源 / 说明 |
|---|---|---|
| Node.js | ≥ 20.18.1 | package.json 的 engines;CI 使用当前 LTS |
| pnpm | 10.12.1 | package.json 的 packageManager,推荐通过 Corepack 启用 |
| Rust | 1.96.0(含 rustfmt、clippy) | rust-toolchain.toml 固定版本,rustup 进入仓库目录时自动安装;Cargo.toml 声明的最低 rust-version 为 1.92 |
| Tauri CLI | 2.12.3 | 作为根 devDependencies 安装,无需全局安装 |
仓库不使用 npm:所有前端命令都应通过 pnpm 执行。
安装 Rust 工具链
# 安装 rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 进入仓库后,rustup 会按 rust-toolchain.toml 自动安装 1.96.0
cd Zephyr
rustc --version # rustc 1.96.0
# 如需为其他架构构建,添加对应 target(作用于固定的 1.96.0 工具链)
rustup target add aarch64-apple-darwin # macOS Apple Silicon
rustup target add x86_64-apple-darwin # macOS Intel
rustup target add aarch64-pc-windows-msvc # Windows ARM64安装 Node.js 与 pnpm
# 任选一种 Node 版本管理器安装 Node 20.18.1+ / 当前 LTS
fnm install --lts # 或 nvm install --lts
# 通过 Corepack 启用 package.json 指定的 pnpm 版本
corepack enable
pnpm --version # 10.12.1平台特定依赖
Windows
| 依赖 | 说明 |
|---|---|
| Visual Studio 2022 Build Tools | 勾选"使用 C++ 的桌面开发"(含 MSVC 与 Windows SDK) |
| WebView2 Runtime | Windows 10(较新版本)/11 通常已预装 |
| NSIS / WiX | 无需手动安装,Tauri CLI 打包时会自动下载 |
macOS
| 依赖 | 说明 |
|---|---|
| Xcode Command Line Tools | xcode-select --install |
| pkg-config、glib(可选) | 交叉编译另一架构时需要,CI 中通过 brew install pkg-config glib 安装 |
DMG 由 Tauri 自带的打包流程生成,无需额外安装 create-dmg。
Linux
以下为 CI 与开发容器(Ubuntu 22.04)实际安装的依赖:
Ubuntu / Debian:
sudo apt update
sudo apt install -y build-essential pkg-config file \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libappindicator3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libssl-dev \
libxdo-dev \
patchelf \
xdg-utils其他发行版可参考 Tauri 官方前置条件:
Fedora:
sudo dnf install webkit2gtk4.1-devel openssl-devel curl wget file \
libappindicator-gtk3-devel librsvg2-devel libxdo-devel
sudo dnf group install "c-development"Arch Linux:
sudo pacman -S --needed webkit2gtk-4.1 base-devel curl wget file openssl \
appmenu-gtk-module libappindicator-gtk3 librsvg xdotool快速开始
1. 克隆仓库
git clone https://github.com/Juwan-Hwang/Zephyr.git
cd Zephyr2. 安装前端依赖
pnpm installpnpm install 会通过 prepare 脚本执行 husky,安装本地 Git 钩子。pnpm-workspace.yaml 的 onlyBuiltDependencies 只允许 esbuild 与 @parcel/watcher 运行安装脚本;CI 采用更严格的 pnpm install --ignore-scripts && pnpm rebuild esbuild @parcel/watcher,本地也可照此执行。
3. 开发模式运行
pnpm dev
# 等价于 pnpm --filter @zephyr/desktop tauri devTauri 会先执行 beforeDevCommand(pnpm run dev:css:复制 @zephyr/shared → 生成 Design Token CSS → UnoCSS watch),再编译并启动 Rust 后端。首次编译需要下载并编译全部 crate,耗时较长;之后为增量编译。
开发模式说明:
- Debug 构建不启用单实例限制,可同时运行多个实例便于测试。
- 应用启动需要 mihomo 内核。最简单的做法是把内核放进
apps/desktop/src-tauri/bundled/(见下一节),或直接放到应用数据目录的core/下(路径见 Installation.md 的"Lite 版内核放置")。 - 需要 WebView 开发者工具时,可启用 crate 的
devtoolsfeature:pnpm tauri dev --features devtools。
4. 生产构建
pnpm build
# 等价于:pnpm --filter @zephyr/desktop build:css
# pnpm --filter @zephyr/desktop tauri buildtauri.conf.json 中 bundle.targets 为 "all",会在当前平台生成该平台支持的全部安装包格式。由于是 Cargo workspace,产物位于仓库根目录的 target/ 下:
| 平台 | 路径 | 格式 |
|---|---|---|
| Windows | target/release/bundle/nsis/ |
Zephyr_<版本>_x64-setup.exe |
| Windows | target/release/bundle/msi/ |
Zephyr_<版本>_x64_zh-CN.msi |
| macOS | target/release/bundle/dmg/ |
.dmg |
| macOS | target/release/bundle/macos/ |
Zephyr.app |
| Linux | target/release/bundle/deb/ |
.deb |
| Linux | target/release/bundle/rpm/ |
.rpm |
| Linux | target/release/bundle/appimage/ |
.AppImage |
使用 --target <triple> 交叉构建时,产物位于 target/<triple>/release/bundle/,例如:
pnpm tauri build --target aarch64-apple-darwin
pnpm tauri build --target aarch64-pc-windows-msvc版本号:仓库中
tauri.conf.json、apps/desktop/package.json与src-tauri/Cargo.toml的版本字段是占位值,正式版本号由发布工作流在构建时按 Git tag 写入。因此本地构建出的应用显示的版本号与正式发布不同。
安装包配置要点
| 平台 | 配置 | 说明 |
|---|---|---|
| Windows NSIS | 语言 SimpChinese、English,不显示语言选择器 |
安装器图标、页眉 Header.bmp、侧栏 Sidebar.bmp |
| Windows MSI | WiX 语言 zh-CN |
文件名带 _zh-CN |
| macOS | minimumSystemVersion: "10.13" |
DMG 使用自定义背景,窗口 660×400 |
| Linux deb | 依赖 libwebkit2gtk-4.1-0、libgtk-3-0、libayatana-appindicator3-1 |
|
| Linux rpm | 依赖 webkit2gtk4.1、gtk3、libayatana-appindicator-gtk3 |
|
| Linux AppImage | tauri.linux.conf.json 的 beforeBundleCommand 安装 scripts/linuxdeploy-plugin-gtk.sh |
剔除 GPU/Mesa 库,并修补 WebKitGTK 辅助进程查找路径,使其在 AppImage 内解析 |
| 全部 | resources:bundled/*、THIRD-PARTY-LICENSES.txt |
许可证文件随所有安装包分发 |
Tauri 构建打包流程
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["pnpm build"] --> B["build:css<br/>copy-shared → build:tokens → UnoCSS<br/>(src/uno-generated.css)"]
B --> C["tauri build<br/>(beforeBuildCommand 再次执行 build:css)"]
C --> D["Cargo 编译 Rust 后端<br/>(release: lto + codegen-units=1)"]
D --> E{"平台?"}
E -- "Windows" --> F1["NSIS .exe"]
E -- "Windows" --> F2["WiX .msi"]
E -- "macOS" --> G1[".app + .dmg"]
E -- "Linux" --> H1[".deb"]
E -- "Linux" --> H2[".rpm"]
E -- "Linux" --> H3[".AppImage<br/>(自定义 GTK 插件)"]
F1 --> I["target/[triple/]release/bundle/"]
F2 --> I
G1 --> I
H1 --> I
H2 --> I
H3 --> I
style A fill:#3498db,stroke:#2980b9,color:#fff
style I fill:#2ecc71,stroke:#27ae60,color:#fffCargo 提供两个发布 profile(定义在根 Cargo.toml):
| Profile | 设置 | 用途 |
|---|---|---|
release(默认) |
opt-level = 3、lto = true、codegen-units = 1、panic = "unwind"、strip = "debuginfo" |
性能优先 |
release-small |
继承 release,opt-level = "z" |
体积优先,需手动指定 |
打包 Full 版(内置内核)
Full 版与 Lite 版使用同一份代码,区别只在于打包时 apps/desktop/src-tauri/bundled/ 是否有内容:
- Full:
bundled/中放入内核与 Geo 数据,随安装包分发;应用首次启动时把它们复制到数据目录的core/(目标文件已存在时不覆盖)。 - Lite:
bundled/为空。
本地打包 Full 版需手动准备以下文件:
| 文件 | 来源 | 说明 |
|---|---|---|
zephyr-mihomo(Windows 为 zephyr-mihomo.exe) |
MetaCubeX/mihomo Releases | 内核二进制须重命名为此名称 |
geoip.dat |
MetaCubeX/meta-rules-dat | GeoIP 数据 |
geosite.dat |
MetaCubeX/meta-rules-dat | GeoSite 规则集 |
Country.mmdb |
MetaCubeX/meta-rules-dat | MaxMind 格式 GeoIP 数据库 |
发布流水线下载的内核资产:
| 平台 / 架构 | mihomo 资产 |
|---|---|
| Windows x64 | mihomo-windows-amd64-compatible-<版本>.zip |
| Windows ARM64 | mihomo-windows-arm64-<版本>.zip |
| macOS x64 | mihomo-darwin-amd64-compatible-<版本>.gz |
| macOS ARM64 | mihomo-darwin-arm64-<版本>.gz |
| Linux x64 | mihomo-linux-amd64-compatible-<版本>.gz |
| Linux ARM64 | mihomo-linux-arm64-<版本>.gz |
资源获取流程(发布流水线)
%%{init: {'themeVariables': {'fontSize': '10px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '10px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 15, 'rankSpacing': 25}}}%%
flowchart LR
A["查询 mihomo<br/>最新正式版 tag"] --> B["下载对应平台内核"]
B --> C["与 GitHub API 返回的<br/>资产 SHA256 digest 比对"]
C --> D["解压并重命名为<br/>zephyr-mihomo(.exe)"]
A2["下载 geoip.dat / geosite.dat /<br/>Country.mmdb + .sha256sum"] --> E["SHA256 校验"]
D --> F["apps/desktop/src-tauri/bundled/"]
E --> F
F --> G["打包 Full 版"]
style C fill:#e74c3c,stroke:#c0392b,color:#fff
style E fill:#e74c3c,stroke:#c0392b,color:#fff
style G fill:#2ecc71,stroke:#27ae60,color:#fff任何一项校验失败都会使构建中止。内核版本即发布构建当时 MetaCubeX/mihomo 的最新正式版。
脚本命令说明
根目录(package.json)
| 命令 | 实际执行 | 用途 |
|---|---|---|
pnpm dev |
pnpm --filter @zephyr/desktop tauri dev |
开发模式 |
pnpm build |
build:css + tauri build(桌面包) |
生产构建 |
pnpm build:css |
桌面包 build:css |
生成样式产物 |
pnpm lint |
pnpm -r lint |
ESLint |
pnpm test |
pnpm -r test |
Vitest 单元测试 |
pnpm typecheck |
pnpm -r typecheck |
tsc 对 JSDoc 做类型检查 |
pnpm tauri <子命令> |
pnpm --filter @zephyr/desktop tauri |
Tauri CLI 通用入口 |
pnpm check:i18n |
node packages/scripts/check-i18n.js |
检查各语言翻译完整性 |
桌面包(apps/desktop/package.json)
| 命令 | 说明 |
|---|---|
build:css |
copy-shared → build:tokens → build:uno |
dev:css |
copy-shared → build:tokens → dev:uno(watch) |
copy-shared |
复制 packages/shared/src/index.js 到 src/_shared/index.js |
build:tokens |
在仓库根目录执行 Style Dictionary,生成 packages/tokens/build/css/ |
copy-tokens |
复制 Token CSS 为 src/tokens-variables.css、src/tokens-theme.css |
build:uno / dev:uno |
UnoCSS CLI 扫描 src/**/*.{html,js},输出 src/uno-generated.css |
build:cm6 |
esbuild 打包 CodeMirror 6 入口为 src/cm6.bundle.js(该文件已提交到仓库,仅在修改编辑器入口时需要重新运行) |
lint |
eslint . |
test |
vitest run |
test:coverage |
vitest run --coverage(lcov + text 报告,输出到 apps/desktop/coverage/) |
typecheck |
tsc -p jsconfig.json --noEmit |
src/_shared/、src/uno-generated.css、src/tokens-*.css 都是生成文件,已被 .gitignore 忽略。
Tauri CLI 常用命令
pnpm tauri dev # 开发模式
pnpm tauri build # 生产构建
pnpm tauri build --debug # 构建 debug 版安装包
pnpm tauri build --bundles app # 仅构建指定格式(如 macOS .app)
pnpm tauri build --target <triple> # 指定目标架构
pnpm tauri info # 查看环境信息代码质量与提交检查
前端
| 工具 | 命令 | 配置 |
|---|---|---|
| ESLint 10 | pnpm lint |
apps/desktop/eslint.config.mjs:浏览器全局变量 + jsyaml、__TAURI__、__TAURI_INTERNALS__;内置 no-unsanitized 规则,检查 innerHTML/outerHTML 赋值和 insertAdjacentHTML 等调用是否经过转义 |
| TypeScript(JSDoc 检查) | pnpm typecheck |
apps/desktop/jsconfig.json |
| Vitest 5 | pnpm test |
apps/desktop/vitest.config.js:happy-dom 环境,匹配 src/**/*.test.js |
| i18n 检查 | pnpm check:i18n |
packages/scripts/check-i18n.js |
后端(在仓库根目录执行)
cargo fmt --all -- --check # 格式检查
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --locked- Clippy 规则集中定义在根
Cargo.toml的[workspace.lints],所有成员 crate 通过[lints] workspace = true继承,本地与 CI 使用同一套规则;.clippy.toml设置认知复杂度阈值 25。 - 快照测试使用
insta;CI 中缺失或过期的快照会导致失败。
依赖与安全审计
| 工具 | 命令 / 配置 | 说明 |
|---|---|---|
| cargo-audit | cd apps/desktop/src-tauri && cargo audit |
忽略列表见 .cargo/audit.toml |
| cargo-deny | cargo deny --manifest-path apps/desktop/src-tauri/Cargo.toml check |
配置 deny.toml |
| Semgrep | semgrep scan --config auto --config .semgrep.yml |
官方规则 + 自定义规则 |
| OSV-Scanner | osv-scanner.toml |
带期限的忽略项 |
| CodeQL、Snyk、GitGuardian、OSSF Scorecard | CI 自动运行 | 见下方工作流列表 |
依赖更新由 Renovate 负责(renovate.json:每周一 6 点前、小版本/补丁分组自动合并,webview2-com 不自动更新,npm 包需发布满 3 天)。
本地 Git 钩子(husky)
| 钩子 | 执行内容 |
|---|---|
pre-commit |
cargo fmt --check --all → pnpm lint → pnpm typecheck |
pre-push |
cargo clippy --workspace --all-targets -- -D warnings → cargo test --workspace → pnpm test(仅删除分支的推送会跳过) |
pre-commit.ci
.pre-commit-config.yaml 只包含快速的文件卫生检查(trailing-whitespace、end-of-file-fixer、check-yaml、check-toml、check-json、check-added-large-files),由 pre-commit.ci 在云端对 PR 运行并自动推送修复提交。Rust/JS 的格式与 lint 不在其中,仍由 CI 负责。本地如需使用:pip install pre-commit && pre-commit run --all-files。
开发工作流
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A[克隆仓库] --> B[pnpm install<br/>同时安装 husky 钩子]
B --> C[pnpm dev<br/>开发调试]
C --> D[git commit]
D --> E["pre-commit 钩子<br/>cargo fmt --check · pnpm lint · pnpm typecheck"]
E --> F[git push]
F --> G["pre-push 钩子<br/>cargo clippy · cargo test · pnpm test"]
G --> H[提交 PR]
H --> I["CI 工作流<br/>前端 / 许可证 / Rust 三平台"]
H --> J["安全扫描<br/>Security Scan · CodeQL · GitGuardian 等"]
H --> K["pre-commit.ci<br/>文件卫生检查"]
I --> L[审查与合并]
J --> L
K --> L第三方许可证文件
apps/desktop/src-tauri/THIRD-PARTY-LICENSES.txt 随所有安装包与便携版分发,也是应用内"设置 → 关于 → 第三方许可证"查看器的数据来源。它由脚本生成,不要手动编辑:
# 前置条件:cargo-about 0.9.2、Rust 工具链、Node.js + pnpm,且已执行 pnpm install
cargo install cargo-about --version 0.9.2 --locked
./scripts/generate-licenses.sh文件由三部分拼接而成:
| 部分 | 来源 |
|---|---|
| Part 1 — Rust 组件 | cargo about generate(about.toml 定义可接受许可证,about.hbs 为模板),基于 Cargo.lock;工作区自身的 crate 会被剔除 |
| Part 2 — JavaScript 组件 | scripts/generate-licenses-js.mjs,读取 pnpm licenses list --prod 的生产依赖并逐个读取包内许可证全文(缺少许可证文本的包会使生成失败) |
| Part 3 — 内置组件与其他材料 | 手工维护的 scripts/third-party-assets-licenses.txt(mihomo 内核、Geo 数据、字体子集等) |
CI 的 licenses 任务会重新生成并在文件与提交版本不一致时失败。修改 Rust 或 JS 依赖(lockfile 变化)后,需要重新运行脚本并提交结果。 发布流水线在构建前也会重新生成一次,确保随包文件与当次构建的 lockfile 完全一致。
CI/CD 与发布流水线
工作流一览
| 工作流 | 触发 | 内容 |
|---|---|---|
ci.yml |
PR、推送到 main | 前端:i18n 检查、ESLint、typecheck、Vitest 覆盖率(上传 Codecov);许可证文件漂移检查;Rust(Ubuntu):fmt、clippy、cargo llvm-cov 测试(上传 Codecov);Rust(Windows、macOS):debug 测试 |
security.yml |
推送到 main/dev、PR、每日定时 | cargo audit、cargo-deny、clippy、fmt、依赖审查、Semgrep、密钥检测、OSSF Scorecard、Tauri 安全配置检查、Linux 构建验证、macOS 构建检查 |
codeql.yml |
推送/PR 到 main、每周定时 | CodeQL 语义分析 |
snyk.yml |
JS 依赖文件变化时 | Snyk 扫描 |
gitguardian.yml |
推送到 main/dev、PR 到 main | GitGuardian 密钥扫描 |
release.yml |
推送 v* tag |
全平台构建、签名并创建草稿 Release |
update-aur.yml / update-scoop.yml / update-winget.yml |
Release 发布后 | 更新 AUR、Scoop bucket、Winget 清单 |
mac-debug.yml |
手动触发(仅仓库所有者) | 在 macOS 26 Runner 上构建 Apple Silicon .app,通过 Tailscale 开放 SSH 与 VNC 远程桌面,用于在真实 Mac 上手动测试未发布代码 |
mobile.yml |
手动触发 | Android APK 与 iOS XCFramework(crates/core-ffi) |
deploy-demo.yml |
前端源码变化时 | 部署 GitHub Pages 在线演示 |
test-badges.yml |
推送到 main | 更新测试数量徽章 |
test-signing.yml |
手动触发 | SignPath 签名流程测试 |
CI 中 Rust 工具链固定为 1.96.0(与 rust-toolchain.toml 同步);Codecov 覆盖率状态目前仅作提示,不阻塞合并(codecov.yml)。
发布构建矩阵
推送 v* tag 后,release.yml 在 6 个矩阵任务上并行构建:
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}}}%%
graph TB
BUILD["release.yml<br/>推送 v* tag"] --> MA["macOS arm64<br/>aarch64-apple-darwin<br/>macos-latest"]
BUILD --> MX["macOS x64<br/>x86_64-apple-darwin<br/>macos-latest"]
BUILD --> LX["Linux x64<br/>ubuntu-22.04"]
BUILD --> LA["Linux arm64<br/>ubuntu-22.04-arm"]
BUILD --> WX["Windows x64<br/>windows-latest"]
BUILD --> WA["Windows arm64<br/>aarch64-pc-windows-msvc<br/>windows-latest"]
MA --> DMG[".dmg(Full / Lite)"]
MX --> DMG
LX --> LIN[".deb / .rpm / .AppImage(Full / Lite)<br/>+ 便携版 tar.gz"]
LA --> LIN
WX --> WIN["NSIS .exe / .msi(Full / Lite)<br/>+ 便携版 zip"]
WA --> WIN
style BUILD fill:#3498db,stroke:#2980b9,color:#fff发布流程
%%{init: {'themeVariables': {'fontSize': '9px', 'nodeBorder': '1px', 'clusterBorder': '1px', 'edgeLabelHeight': '8px'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 12, 'rankSpacing': 15}}}%%
flowchart TD
A["从 tag 解析版本号<br/>写入 tauri.conf.json / Cargo.toml / package.json"] --> B["安装 pnpm、Node LTS、Rust<br/>pnpm install --ignore-scripts"]
B --> C["cargo-about 重新生成<br/>THIRD-PARTY-LICENSES.txt"]
C --> D["下载并校验 mihomo + Geo 数据<br/>→ bundled/"]
D --> E["构建 Full 版"]
E --> F["清空 bundled/<br/>构建 Lite 版"]
F --> G{"平台"}
G -- "Linux" --> H["Full AppImage + core/ + .portable<br/>→ 便携版 tar.gz"]
G -- "Windows" --> I["SignPath 签名 NSIS / MSI / 便携版 exe<br/>校验签名者为 SignPath Foundation"]
I --> J["签名后的 exe + core/ + .portable<br/>→ 便携版 zip"]
H --> K["Minisign 为全部产物生成 .minisig"]
J --> K
G -- "macOS" --> K
K --> L["上传到草稿 Release"]
L --> M["SBOM 任务:cargo-cyclonedx + cdxgen<br/>合并为 v<版本>.cdx.json 附加到 Release"]
M --> N["人工发布 Release"]
N --> O["自动更新 AUR / Scoop / Winget"]要点:
- 发布任务持有签名密钥,因此不使用依赖缓存,每次都干净安装。
- Full 与 Lite 在同一任务中先后构建;便携版复用 Full 构建的二进制,不额外编译。
- Windows 产物通过 SignPath 签名(证书由 SignPath Foundation 提供),流水线逐个校验 Authenticode 签名有效且签名者为 SignPath Foundation 后才替换未签名文件。详见仓库
CODE_SIGNING_POLICY.md。 - macOS 产物未经 Apple 开发者证书签名与公证。
- 所有产物另附 Minisign(Ed25519)签名
.minisig,客户端内更新会校验该签名。 - Release 以草稿形式创建,由维护者手动发布;发布事件再触发 AUR / Scoop / Winget 更新。
发布产物命名
| 平台 | Full / Lite | 便携版 |
|---|---|---|
| Windows | Zephyr_<版本>_{x64,arm64}-setup-{full,lite}.exeZephyr_<版本>_{x64,arm64}_zh-CN-{full,lite}.msi |
Zephyr_{x64,arm64}-portable.zip |
| macOS | Zephyr_<版本>_{aarch64,x64}-{full,lite}.dmg |
— |
| Linux | Zephyr_<版本>_{amd64,aarch64}-{full,lite}.AppImageZephyr_<版本>_{amd64,arm64}-{full,lite}.debZephyr-<版本>-1.{x86_64,aarch64}-{full,lite}.rpm |
Zephyr-linux-{x64,arm64}-portable.tar.gz |
| 通用 | 每个文件对应一个 .minisig;SBOM v<版本>.cdx.json |
开发容器
仓库提供 .devcontainer/,可在 VS Code / GitHub Codespaces 中一键获得 Linux 构建环境:
| 项目 | 配置 |
|---|---|
| 基础镜像 | mcr.microsoft.com/devcontainers/base:ubuntu-22.04 |
| 工具 | Rust(stable,进入仓库后按 rust-toolchain.toml 切换到 1.96.0)、Node LTS |
| 初始化脚本 | post-create.sh:安装 WebKitGTK 等系统依赖 → corepack enable → pnpm install --ignore-scripts → pnpm rebuild esbuild @parcel/watcher |
| VS Code 扩展 | rust-analyzer、Tauri、ESLint、Tailwind CSS IntelliSense |
由于使用
--ignore-scripts,容器内不会自动安装 husky 钩子;如需钩子,手动执行pnpm exec husky。
常见构建问题
Q: cargo 报错 "linker 'link.exe' not found"(Windows)
A: 安装 Visual Studio Build Tools,勾选"使用 C++ 的桌面开发"(包含 MSVC 与 Windows SDK),然后重新打开终端。
Q: 编译时提示找不到 webkit2gtk(Linux)
A: 安装 WebKitGTK 4.1 开发包(Ubuntu/Debian:libwebkit2gtk-4.1-dev;Fedora:webkit2gtk4.1-devel;Arch:webkit2gtk-4.1),完整列表见平台特定依赖。
Q: rustc 版本与预期不符 / clippy 结果和 CI 不一致
A: 仓库通过 rust-toolchain.toml 固定 1.96.0。确认在仓库目录内执行 rustc --version;若显示其他版本,检查是否设置了 RUSTUP_TOOLCHAIN 环境变量或 rustup override。
Q: CI 的 "Third-party licenses (up to date)" 失败
A: 依赖变化后许可证文件过期。安装 cargo-about 0.9.2,执行 pnpm install 和 ./scripts/generate-licenses.sh,提交更新后的 apps/desktop/src-tauri/THIRD-PARTY-LICENSES.txt。
Q: pnpm dev 启动后内核无法运行
A: 源码中不包含 mihomo 内核。把内核(重命名为 zephyr-mihomo / zephyr-mihomo.exe)放入 apps/desktop/src-tauri/bundled/ 或应用数据目录的 core/ 下,见打包 Full 版。
Q: 首次编译时间过长
A: 首次需要下载并编译全部依赖,release profile 还开启了 LTO 与 codegen-units = 1,耗时明显长于 debug 构建。可以:
- 日常调试使用
pnpm dev(debug 构建); - 使用
sccache:cargo install sccache && export RUSTC_WRAPPER=sccache; - 配置 crates.io 镜像加速下载(见下一问)。
Q: 如何配置 Cargo 镜像源加速下载?
A: 在 ~/.cargo/config.toml 中添加:
[source.crates-io]
replace-with = 'ustc'
[source.ustc]
registry = "sparse+https://mirrors.ustc.edu.cn/crates.io-index/"Q: macOS 本地构建的应用打不开 / 签名相关报错
A: 仓库未配置 Apple 签名身份,本地构建的 .app 未签名。在本机运行通常没有问题;若从其他机器拷贝过来被 Gatekeeper 拦截,可执行 xattr -cr /path/to/Zephyr.app。如需临时 ad-hoc 签名,可在 tauri.conf.json 的 bundle.macOS 中设置 "signingIdentity": "-"。
Q: 如何构建其他架构?
A: 先 rustup target add <triple>,再执行 pnpm tauri build --target <triple>。同一操作系统内跨架构(如 macOS arm64 ↔ x64、Windows x64 → arm64)可行;跨操作系统构建建议使用发布流水线。macOS 交叉编译需要 brew install pkg-config glib 并设置 PKG_CONFIG_ALLOW_CROSS=1。
构建完成后,建议阅读 Contributing.md 了解代码规范和提交流程。