Chapter 09

打包与代码签名

生成各平台安装包,完成代码签名与公证,GitHub Actions 自动化发布

tauri build 基础

# 构建当前平台的安装包
npm run tauri build

# 指定安装包格式
npm run tauri build -- --bundles nsis     # Windows NSIS 安装向导
npm run tauri build -- --bundles msi      # Windows MSI
npm run tauri build -- --bundles dmg      # macOS DMG
npm run tauri build -- --bundles appimage # Linux AppImage
npm run tauri build -- --bundles deb      # Debian/Ubuntu .deb

# 调试构建(不启用优化,保留符号)
npm run tauri build -- --debug

# 构建产出目录
# src-tauri/target/release/bundle/
# ├── macos/    → .app, .dmg
# ├── nsis/     → *-setup.exe
# ├── msi/      → *.msi
# └── appimage/ → *.AppImage

tauri.conf.json 打包配置

{
  "bundle": {
    "active": true,
    "targets": "all",
    "identifier": "com.mycompany.myapp",
    "publisher": "My Company",
    "copyright": "Copyright © 2024 My Company",
    "category": "DeveloperTool",
    "shortDescription": "My awesome desktop app",
    "longDescription": "A longer description for app stores",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ],
    "windows": {
      "wix": { "language": "zh-CN" },
      "nsis": {
        "languages": ["SimpChinese", "English"],
        "installMode": "currentUser"
      }
    },
    "macOS": {
      "signingIdentity": "Developer ID Application: My Company (XXXXXXXXXX)",
      "entitlements": "entitlements.plist",
      "minimumSystemVersion": "10.15"
    },
    "linux": {
      "deb": {
        "depends": ["libwebkit2gtk-4.1-0", "libayatana-appindicator3-1"]
      }
    }
  }
}

macOS 代码签名与公证

代码签名
用 Apple Developer ID Application 证书对应用二进制文件和动态库进行签名。未签名的应用在 macOS Gatekeeper 下会被阻止运行。
公证(Notarization)
将已签名的 .app 提交给 Apple 服务器扫描恶意软件。公证通过后,应用可在任何 Mac 上运行,即使是没有 App Store 的情况。
Stapling
将公证票据"装订"到安装包中,使离线环境也能验证公证状态,无需联网查询 Apple 服务器。
# 本地 macOS 签名所需环境变量
export APPLE_CERTIFICATE="base64编码的p12证书"
export APPLE_CERTIFICATE_PASSWORD="证书密码"
export APPLE_SIGNING_IDENTITY="Developer ID Application: My Company (XXXXXXXXXX)"

# 公证所需(使用 App Store Connect API Key)
export APPLE_API_ISSUER="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
export APPLE_API_KEY="XXXXXXXXXX"
export APPLE_API_KEY_PATH="/path/to/AuthKey_XXXXXXXXXX.p8"

# tauri build 会自动读取这些环境变量完成签名+公证
npm run tauri build

Windows 代码签名

# Windows 签名所需环境变量
# 方式1:使用 .pfx 证书文件
export TAURI_SIGNING_PRIVATE_KEY="base64编码的pfx"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="证书密码"

# 方式2:使用 Azure Trusted Signing(推荐,无需本地存储证书)
# 在 tauri.conf.json 中配置 signtool 选项

GitHub Actions 跨平台矩阵构建

# .github/workflows/release.yml
name: Release

on:
  push:
    tags: ['v*']

jobs:
  release:
    permissions:
      contents: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: macos-latest
            args: '--target aarch64-apple-darwin'  # Apple Silicon
          - platform: macos-latest
            args: '--target x86_64-apple-darwin'    # Intel Mac
          - platform: ubuntu-22.04
            args: ''
          - platform: windows-latest
            args: ''

    runs-on: ${{ matrix.platform }}
    steps:
      - uses: actions/checkout@v4

      - name: Install Linux dependencies
        if: matrix.platform == 'ubuntu-22.04'
        run: |
          sudo apt-get update
          sudo apt-get install -y libwebkit2gtk-4.1-dev \
            build-essential curl wget file \
            libxdo-dev libssl-dev \
            libayatana-appindicator3-dev librsvg2-dev

      - uses: actions/setup-node@v4
        with:
          node-version: lts/*
          cache: npm

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}

      - uses: Swatinem/rust-cache@v2
        with:
          workspaces: 'src-tauri -> target'

      - run: npm install

      - uses: tauri-apps/tauri-action@v0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # macOS 签名
          APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
          APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
          APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
          APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
          APPLE_API_KEY_PATH: ${{ secrets.APPLE_API_KEY_PATH }}
        with:
          tagName: ${{ github.ref_name }}
          releaseName: 'v__VERSION__'
          releaseBody: 'See CHANGELOG.md for release notes.'
          releaseDraft: true
          prerelease: false
          args: ${{ matrix.args }}
tauri-action 的便利性

tauri-apps/tauri-action GitHub Action 会自动完成前端构建、Rust 编译、代码签名、公证、生成安装包,并将产物上传到 GitHub Release,是最简单的发布方式。使用 tagName 中的 __VERSION__ 占位符会自动替换为 tauri.conf.json 中的版本号。

macOS Universal Binary

同时构建 Apple Silicon (arm64) 和 Intel (x86_64) 两个版本是最佳实践,而非合并为 Universal Binary——这样可以减小每个安装包的体积,用户按自己的架构下载对应版本。在矩阵中分别指定 --target aarch64-apple-darwin--target x86_64-apple-darwin

代码签名的技术原理

为什么需要代码签名?

代码签名不只是"为了绕过系统警告",而是现代操作系统安全体系的重要组成部分:

macOS Gatekeeper
Gatekeeper 在用户打开下载的应用时检查:是否有有效的开发者证书签名?是否通过 Apple 公证(Notarization)?公证会将应用的哈希上传到 Apple 服务器,Apple 扫描是否含有恶意代码后返回"公证票据"。签名+公证确保了"这个应用来自已知开发者,且未被篡改"。未签名的应用在 macOS 12+ 默认完全无法打开。
Windows Authenticode
Windows 使用 Authenticode 签名(微软标准)。未签名的 EV Code Signing Certificate 会触发 SmartScreen 警告("Windows 已保护你的电脑"),许多用户会直接关闭。EV(Extended Validation)证书需要硬件安全令牌(YubiKey 等),价格较贵但立即获得 SmartScreen 信任;OV 证书需要积累信任分数,初期仍可能有警告。
证书链(Certificate Chain)
代码签名证书是一个信任链:根证书(Apple/DigiCert 等 CA)→ 中间证书 → 开发者证书。操作系统信任根 CA,因此信任整条链上的证书。证书过期后,签名失败——需要每年/2年续期。GitHub Actions 的 secrets 存储证书的 base64 编码,CI 时解码并安装到临时 keychain。
Linux 发行版的签名
Linux 没有统一的签名机制,但各包格式有自己的方式:.deb 用 dpkg-sig,.rpm 用 RPM GPG 签名,Snap 使用 Canonical 的签名服务器,Flatpak 内置签名验证。Tauri 目前的 Linux 包(AppImage/deb/rpm)不强制要求签名,但建议提供 GPG 校验文件(.asc)。

GitHub Actions 签名的安全实践

签名 Secrets 的安全管理:

绝不要:
  ✗ 把证书文件(.p12, .pfx)提交到 git 仓库
  ✗ 在 Actions 日志中打印证书密码
  ✗ 把私钥存储在公开 Secrets(用 repository secrets,非 environment secrets)

推荐做法:
  ✓ 证书用 base64 编码后存储到 GitHub Secrets
    base64 -i certificate.p12 | pbcopy  (macOS)
  ✓ 限制 Actions workflow 只能在特定分支(main/release/*)触发
  ✓ 为 Release 构建单独创建 GitHub Environment(production),
    限制只有特定 reviewers 能审批才会使用签名 Secrets
  ✓ 使用 GitHub OIDC + Apple Notary Tool(避免存储 Apple ID 密码)

苹果公证的新方式(推荐):
  旧方式(altool):需要存储 Apple ID + app-specific password
  新方式(notarytool + API Key):只需要存储 API Key(可以随时吊销)
  格式:APPLE_API_KEY_ID + APPLE_API_ISSUER + APPLE_API_KEY(私钥文件)
证书过期的处理

代码签名证书通常有效期 1-2 年。证书过期后,已经签名的旧版本仍然有效(签名时的时间戳被公证服务器记录),但新版本无法再签名,必须续期。建议在 Tauri 项目的 README 中记录证书过期时间,并在日历中设置提前 30 天的提醒,以免影响正常发布。

构建缓存优化

Tauri 的 CI 构建主要耗时在 Rust 编译上,合理配置缓存可以将构建时间从 20 分钟缩短到 3-5 分钟:

# GitHub Actions 缓存配置
- name: Cache Rust dependencies
  uses: Swatinem/rust-cache@v2
  with:
    workspaces: 'src-tauri -> target'
    # 仅缓存 release profile 的依赖
    cache-on-failure: true

- name: Cache Node modules
  uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-
使用 sccache 加速跨 job 缓存

sccache(Shared Compilation Cache)是 Mozilla 开发的编译缓存工具,可以在不同 CI job 之间共享 Rust 编译缓存,存储到 S3 或 GitHub Actions 缓存中。对于 Tauri 项目,sccache 可以将矩阵中各平台的编译时间都大幅缩短,特别是当 Rust 依赖没有变化时。

Linux 包格式选择

AppImage
单文件可执行包,无需安装,在所有 Linux 发行版上运行。最通用的格式,适合独立分发。缺点是文件较大(包含运行时依赖),不会通过包管理器更新。
.deb (Debian/Ubuntu)
Debian 系包格式,可通过 apt/dpkg 安装和管理。支持声明系统依赖(如 libwebkit2gtk),在 Ubuntu 系发行版上用户体验最好。适合发布到 Launchpad PPA。
.rpm (Fedora/CentOS)
Red Hat 系包格式,通过 dnf/rpm 管理。适合企业 Linux 环境,需要在 Fedora/RHEL 系机器上构建。
Flatpak / Snap
沙箱化的应用格式,可发布到 Flathub(Flatpak)和 Snap Store。Tauri 官方不直接支持,需要手动配置沙箱权限,可能需要调整文件系统权限声明。

本章小结

本章核心要点