Skip to content

GitHub Actions ​

GitHub Actions 工作流可以在你推送标签时构建所有平台的应用、为其签名,并把它们连同更新文件上传到一个草稿 release,供你发布。每个平台在自己的 runner 上同时构建。MyGo 不需要 cgo,因此 Ubuntu runner 既能构建 Linux 应用也能构建 Windows 应用;macOS 应用需要 macOS runner,因为签名、公证和磁盘镜像需要 macOS。

工作流 ​

在配置中设置了 updates.github 后,把以下内容保存为 .github/workflows/release.yml:

yaml
name: Release

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

permissions:
  contents: write # the draft release, and the uploads to it

jobs:
  draft-release:
    runs-on: ubuntu-latest
    steps:
      - name: Create the draft release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          if ! gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
            gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" \
              --draft --verify-tag --title "$GITHUB_REF_NAME" --generate-notes
          fi

  build:
    needs: draft-release
    name: ${{ matrix.name }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - { name: macOS, os: macos-latest, platform: "darwin/arm64,darwin/amd64" }
          - { name: Windows, os: ubuntu-latest, platform: "windows/amd64,windows/arm64" }
          - { name: Linux, os: ubuntu-latest, platform: "linux/amd64,linux/arm64" }
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-go@v7
        with:
          go-version-file: go.mod
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - name: Install NSIS, for the Windows installers
        if: matrix.name == 'Windows' && runner.os == 'Linux'
        run: sudo apt-get update && sudo apt-get install -y nsis
      - name: Build and upload
        env:
          GH_TOKEN: ${{ github.token }}
          MYGO_UPDATER_PRIVATE_KEY: ${{ secrets.MYGO_UPDATER_PRIVATE_KEY }}
        run: bun run build -- -platform "${{ matrix.platform }}" -upload

原生 UI项目没有前端,因此不需要 Bun:省略 setup-bun 和 bun install 步骤,改用 go.mod 锁定为 tool 的 CLI 构建:

yaml
        run: go tool mygo build -platform "${{ matrix.platform }}" -upload

把为更新签名的私钥,即 mygo-update.key 的内容(见自动更新),添加到仓库的 secrets 中:

sh
gh secret set MYGO_UPDATER_PRIVATE_KEY < path/to/mygo-update.key

然后提升配置中的 version,提交并推送对应的标签:

sh
git tag v1.2.0
git push origin v1.2.0

draft release 作业创建草稿 release v1.2.0。随后 build 作业为其矩阵的每一行各运行一次,同时进行,每次构建都上传到该草稿:macOS 磁盘镜像、Windows 安装程序、Linux 归档包、Debian 包和安装脚本,各自连同其更新文件。检查草稿并发布它:应用随即可更新到该版本。

  • 无论工作流由哪个标签触发,mygo build 都上传到配置版本所对应的 release,其标签带 updates 的 tagPrefix(v 为默认)。可以添加一个步骤检查两者一致,见下文。
  • 草稿在构建开始前就已存在,因此构建会上传到它:没有草稿时,每个 mygo build -upload 都会创建自己的草稿。重新运行工作流会沿用该草稿,并替换其中的文件。
  • 设置 fail-fast: false 后,某个平台失败时其他平台仍会完成。使用"Re-run failed jobs"会只重新构建失败的那个,并上传到同一个草稿。
  • 矩阵某一行的 platform 可以列出多个目标,mygo build 会依次构建它们。为每个目标(如 windows/amd64 和 windows/arm64)各设一行,则可以在更多 runner 上同时构建。
  • release 页面使用 GitHub 根据上次 release 以来的 pull request 生成的说明,你可以在草稿中编辑它。更新窗口显示的则是 CHANGELOG.md 中该版本的小节,由更新清单携带。
  • 工作流的 GITHUB_TOKEN 让 runner 自带的 gh 能够创建 release 并上传到它。使用 tagPrefix 时,它还能让 mygo build 找到增量更新所基于的更早 release。

按目前的样子,工作流构建出的应用能运行,但没有为其他机器签名:macOS 应用是 ad hoc 签名,在其他 Mac 上会被 Gatekeeper 拦截;Windows 应用没有签名,SmartScreen 会警告用户。下面的小节会为它们签名。

为 macOS 应用签名并公证 ​

macOS 那一行需要你的 Developer ID 证书和 Apple 公证服务的凭据(见签名与公证)。从"钥匙串访问"导出"Developer ID Application"证书及其私钥,存为带密码的 .p12 文件,并添加以下 secrets:

sh
base64 -i certificate.p12 | gh secret set MACOS_CERTIFICATE
gh secret set MACOS_CERTIFICATE_PASSWORD   # the password of the .p12
gh secret set APPLE_ID                     # the Apple ID of your developer account
gh secret set APPLE_TEAM_ID                # your team ID, as in the identity's (TEAMID)
gh secret set APPLE_APP_PASSWORD           # an app-specific password of the Apple ID

在 account.apple.com 的"登录与安全"下创建 App 专用密码。然后,在"Build and upload"步骤之前,把它们导入 macOS runner 的钥匙串:

yaml
      - name: Import the certificate and the notary credentials
        if: runner.os == 'macOS'
        env:
          MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
        run: |
          keychain="$RUNNER_TEMP/signing.keychain-db"
          password="$(openssl rand -base64 24)"
          security create-keychain -p "$password" "$keychain"
          security set-keychain-settings -lut 21600 "$keychain"
          security unlock-keychain -p "$password" "$keychain"
          echo "$MACOS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
          security import "$RUNNER_TEMP/certificate.p12" -k "$keychain" \
            -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
          rm "$RUNNER_TEMP/certificate.p12"
          security set-key-partition-list -S apple-tool:,apple: -s -k "$password" "$keychain"
          security list-keychains -d user -s "$keychain" login.keychain
          xcrun notarytool store-credentials notary --keychain "$keychain" \
            --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_PASSWORD"
          echo "NOTARY_KEYCHAIN=$keychain" >> "$GITHUB_ENV"

该钥匙串与作业同生命周期,codesign 会在其中找到证书。配置使用证书签名、使用 profile 公证:在工作流中从该钥匙串读取,在你的 Mac 上则从登录钥匙串读取——那里没有设置 NOTARY_KEYCHAIN:

ts
export default defineConfig({
  macos: {
    signingIdentity: "Developer ID Application: Jane Doe (TEAMID)",
    notarize: { keychainProfile: "notary", keychain: process.env.NOTARY_KEYCHAIN },
  },
});

此后 mygo build 会为应用签名,把磁盘镜像提交给公证服务并等待结果——通常需要几分钟——然后钉扎票据。也可以用 App Store Connect API 密钥代替 Apple ID:通过 --key、--key-id 和 --issuer 把密钥文件、ID 和发行者传给 notarytool store-credentials。

为 Windows 应用签名 ​

在 Ubuntu runner 上,osslsigncode 用 .pfx 证书为 Windows 应用及其安装程序签名。把证书和它的密码添加到 secrets:

sh
base64 -i code-signing.pfx | gh secret set WINDOWS_CERTIFICATE
gh secret set WINDOWS_CERTIFICATE_PASSWORD

对于 Windows 那一行,与 NSIS 一起安装 osslsigncode,把证书写入文件,并将路径和密码交给构建:

yaml
      - name: Install NSIS and osslsigncode
        if: matrix.name == 'Windows' && runner.os == 'Linux'
        run: sudo apt-get update && sudo apt-get install -y nsis osslsigncode
      - name: Write the certificate
        if: matrix.name == 'Windows'
        env:
          WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
        run: |
          echo "$WINDOWS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/code-signing.pfx"
          echo "WINDOWS_CERTIFICATE_FILE=$RUNNER_TEMP/code-signing.pfx" >> "$GITHUB_ENV"
      - name: Build and upload
        env:
          GH_TOKEN: ${{ github.token }}
          MYGO_UPDATER_PRIVATE_KEY: ${{ secrets.MYGO_UPDATER_PRIVATE_KEY }}
          MYGO_WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
        run: bun run build -- -platform "${{ matrix.platform }}" -upload
ts
export default defineConfig({
  windows: { certificate: process.env.WINDOWS_CERTIFICATE_FILE },
});

第一步替换原先只安装 NSIS 的步骤。在你机器上的构建没有该变量,不会签名。

在 Windows 上签名 ​

私钥保存在硬件令牌或云服务(如 Azure Trusted Signing)中的证书,要用 signCommand 签名(见代码签名),它通常需要 signtool。此时应在 Windows runner 上构建 Windows 应用:它有 signtool,且 mygo build 会自行下载 NSIS;安装 NSIS 的步骤只用于 Linux runner:

yaml
          - { name: Windows, os: windows-latest, platform: "windows/amd64,windows/arm64" }

按照签名服务文档的说明,在 Windows 那一行"Build and upload"之前的步骤中登录该服务。除非设置 shell: bash,否则步骤在 Windows runner 上用 PowerShell 运行。

全新的 runner 每次运行时 mygo build 都会重新下载 NSIS。在"Build and upload"之前加一个步骤把它存入 Actions 缓存,构建就既不需要下载,也不需要访问下载源主机。锁文件锁定了 mygo-cli,mygo-cli 又锁定了 NSIS:

yaml
      - name: Cache NSIS
        if: runner.os == 'Windows'
        uses: actions/cache@v6
        with:
          path: ~\AppData\Local\mygo\nsis-*
          key: nsis-${{ hashFiles('bun.lock') }}

在原生 UI 项目中,go.mod 锁定 CLI:缓存的 key 应基于 hashFiles('go.sum')。

检查版本 ​

与配置版本不匹配的标签会把构建发布成另一个版本。请把版本保存在 package.json 中,由配置读取(见计算配置):

ts
import pkg from "./package.json" with { type: "json" };

export default defineConfig({
  version: pkg.version,
});

并在 draft release 作业创建草稿之前检查它,使错误的标签不触发任何构建:

yaml
      - uses: actions/checkout@v7
      - name: Check the version
        run: test "$GITHUB_REF_NAME" = "v$(jq -r .version package.json)"

原生 UI 项目的版本在 mygo.json 中:请改为检查 jq -r .version mygo.json。

发布 release ​

你可以查看草稿后自行发布,也可以让工作流在所有作业都上传完成后自动发布:

yaml
  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - env:
          GH_TOKEN: ${{ github.token }}
        run: gh release edit "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --draft=false

使用 desktop-v 这样的 tagPrefix 时,请让工作流由它的标签触发(tags: ["desktop-v*"]),检查标签是否等于 desktop-v 加版本号,并用 --latest=false 创建和发布 release,使仓库的最新 release 仍属于其他 release(见构建与发布)。

发布预发布版本的标签(如 v1.3.0-beta.1)时也要加上 --prerelease,这样只有用户选择了预发布的应用才会更新到它(预发布版本)。

发布到 S3 ​

配置了 updates.s3 时,mygo build -upload 会改为上传到存储桶(见发布到 S3)。用存储桶的凭据代替 GH_TOKEN 传给各作业:

yaml
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          MYGO_UPDATER_PRIVATE_KEY: ${{ secrets.MYGO_UPDATER_PRIVATE_KEY }}

没有草稿 release:省略 draft release 作业以及 build 作业的 needs。某个平台的清单被其作业上传的那一刻,该平台的更新即已发布,工作流也不再需要 contents: write 权限。

不使用 release ​

没有 updates 时,-upload 无处可传。请不带它构建,并把安装程序保留为本次运行的 artifact;这也适用于每次 push,用来检查应用能否构建:

yaml
      - run: bun run build -- -platform "${{ matrix.platform }}"
      - uses: actions/upload-artifact@v7
        with:
          name: ${{ matrix.name }}
          path: |
            build/*/*.dmg
            build/*/*-setup-*.exe
            build/*/*.deb
            build/*/*.tar.gz

build 是新项目配置中 out 的默认值。