macOS 按 Apple Silicon 和 Intel 分别发布安装包

发布流程改为按 aarch64、x86_64 构建矩阵,分别签名、公证并上传 DMG 和升级 ZIP,
不再合成 universal 应用。两个任务使用独立缓存和产物名称,发布时收集全部架构。

更新清单保留原有平台键,分别记录对应架构包的地址、大小和 SHA-256。
已有 universal 客户端按运行架构升级;Rosetta 下的 Intel 版仍接收 Intel 包。
同步更新中英文下载说明、发布文档和示例清单,增加双架构校验与错包拒绝断言。

验证:52 项更新模块测试通过,Clippy、格式、YAML 和 shell 语法检查通过。
This commit is contained in:
bigtan authored and since2006 committed 2026-10-10 22:33:22 +08:00
1 parent 5ca6e53366
commit e91bab0d09
8 files changed
+77 -44

No files matched your search

+17 -13
View File
@@ -1,7 +1,7 @@
# A release: push a tag `v<version>` matching Cargo.toml's version.
#
# verify the tag, the CHANGELOG section, the update keys
# macos universal app: signed, notarized, stapled; update zip and DMG
# macos per-architecture apps: signed, notarized, stapled; update zip and DMG
# windows per-user Inno Setup installer (unsigned for now)
# linux AppImage
# publish (environment `release`, approved by hand) uploads to R2 behind
@@ -83,8 +83,13 @@ jobs:
macos:
needs: verify
runs-on: macos-14
strategy:
fail-fast: false
matrix:
arch: [aarch64, x86_64]
timeout-minutes: 120
env:
ARCH: ${{ matrix.arch }}
VERSION: ${{ needs.verify.outputs.version }}
SHELLRS_UPDATE_CHANNEL: ${{ needs.verify.outputs.channel }}
SHELLRS_COMMIT: ${{ github.sha }}
@@ -92,18 +97,17 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install
- run: rustup target add aarch64-apple-darwin x86_64-apple-darwin
- run: rustup target add "$ARCH-apple-darwin"
- uses: Swatinem/rust-cache@v2
- name: Build both halves and join them
with:
key: macos-${{ matrix.arch }}
- name: Build the architecture-specific app
run: |
export SHELLRS_BUILD_DATE=$(date -u +%Y-%m-%d)
cargo build --release --locked --bin shellrs --target aarch64-apple-darwin
cargo build --release --locked --bin shellrs --target x86_64-apple-darwin
cargo build --release --locked --bin shellrs --target "$ARCH-apple-darwin"
mkdir -p dist build
lipo -create -output build/shellrs \
target/aarch64-apple-darwin/release/shellrs \
target/x86_64-apple-darwin/release/shellrs
packaging/macos/bundle.sh build/shellrs "$VERSION" "$GITHUB_RUN_NUMBER" build
lipo -verify_arch "${ARCH/aarch64/arm64}" "target/$ARCH-apple-darwin/release/shellrs"
packaging/macos/bundle.sh "target/$ARCH-apple-darwin/release/shellrs" "$VERSION" "$GITHUB_RUN_NUMBER" build
- name: Sign
env:
CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_P12 }}
@@ -147,12 +151,12 @@ jobs:
xcrun stapler staple build/ShellRS.app
# The update package: the stapled bundle, zipped the way the
# updater unzips it (ditto keeps the signature intact).
ditto -c -k --keepParent build/ShellRS.app "dist/ShellRS-$VERSION-macos-universal.app.zip"
ditto -c -k --keepParent build/ShellRS.app "dist/ShellRS-$VERSION-macos-$ARCH.app.zip"
# The first-install disk image, with the Applications shortcut.
mkdir -p "$RUNNER_TEMP/dmg"
cp -R build/ShellRS.app "$RUNNER_TEMP/dmg/"
ln -s /Applications "$RUNNER_TEMP/dmg/Applications"
dmg="dist/ShellRS-$VERSION-macos-universal.dmg"
dmg="dist/ShellRS-$VERSION-macos-$ARCH.dmg"
# hdiutil's own estimate of the image size once came out too small
# for the app ("No space left on device"), so the size is given,
# with room to spare; the compressed image is about half the app.
@@ -171,7 +175,7 @@ jobs:
spctl -a -vv -t install "$dmg"
- uses: actions/upload-artifact@v4
with:
name: macos
name: macos-${{ matrix.arch }}
path: dist/*
windows:
@@ -265,7 +269,7 @@ jobs:
- name: Collect
run: |
mkdir -p dist
cp artifacts/macos/* artifacts/windows/* artifacts/linux/* dist/
cp artifacts/macos-aarch64/* artifacts/macos-x86_64/* artifacts/windows/* artifacts/linux/* dist/
cp artifacts/notes/notes.md notes.md
ls -l dist
- name: Signing key
+2 -1
View File
@@ -119,7 +119,8 @@ Download from [shellrs.com/download](https://shellrs.com/download), which offers
| System | Package | Notes |
| --- | --- | --- |
| macOS 11+ (Apple Silicon and Intel) | `ShellRS-<version>-macos-universal.dmg` | Signed and notarized. Open the DMG and drag ShellRS into Applications |
| macOS 11+ (Apple Silicon) | `ShellRS-<version>-macos-aarch64.dmg` | Signed and notarized. Open the DMG and drag ShellRS into Applications |
| macOS 11+ (Intel) | `ShellRS-<version>-macos-x86_64.dmg` | Signed and notarized. Open the DMG and drag ShellRS into Applications |
| Windows x64 | `ShellRS-<version>-windows-x86_64-setup.exe` | Installs per user, no administrator rights needed. The installer is not signed yet; if SmartScreen stops it, choose "More info › Run anyway" |
| Linux x64 | `ShellRS-<version>-linux-x86_64.AppImage` | `chmod +x` and run. Needs glibc 2.35 or newer (Ubuntu 22.04, Debian 12 and later) |
+2 -1
View File
@@ -119,7 +119,8 @@ ShellRS 把 Xshell 式的主机管理和多标签终端、WinSCP 式的双栏 SF
| 系统 | 安装包 | 说明 |
| --- | --- | --- |
| macOS 11+(Apple Silicon 和 Intel) | `ShellRS-<版本>-macos-universal.dmg` | 已签名并经过 Apple 公证。打开 DMG,把 ShellRS 拖进「应用程序」 |
| macOS 11+(Apple Silicon) | `ShellRS-<版本>-macos-aarch64.dmg` | 已签名并经过 Apple 公证。打开 DMG,把 ShellRS 拖进「应用程序」 |
| macOS 11+(Intel) | `ShellRS-<版本>-macos-x86_64.dmg` | 已签名并经过 Apple 公证。打开 DMG,把 ShellRS 拖进「应用程序」 |
| Windows x64 | `ShellRS-<版本>-windows-x86_64-setup.exe` | 按当前用户安装,不需要管理员权限。安装程序暂未签名,SmartScreen 拦下时点「更多信息 › 仍要运行」 |
| Linux x64 | `ShellRS-<版本>-linux-x86_64.AppImage` | `chmod +x` 后直接运行,需要 glibc 2.35 以上(Ubuntu 22.04、Debian 12 及更新的版本) |
+3 -1
View File
@@ -5,7 +5,7 @@
在 `CHANGELOG.md` 里写好 `## [x.y.z]` 一节,把 `Cargo.toml` 的版本改成 `x.y.z`,提交后推送 tag `vx.y.z`。`.github/workflows/release.yml` 依次:
1. 核对 tag 与 `Cargo.toml` 一致,截出更新说明,确认代码里有发布公钥;
2. 构建并打包:macOS 合成 universal,签名、公证、staple,产出升级用的 `.app.zip` 和首次安装用的 `.dmg`;Windows 用 Inno Setup 打安装程序;Linux 打 AppImage;
2. 构建并打包:macOS 按 `aarch64`(Apple Silicon)和 `x86_64`(Intel)分别构建、签名、公证、staple,各自产出升级用的 `.app.zip` 和首次安装用的 `.dmg`;Windows 用 Inno Setup 打安装程序;Linux 打 AppImage;
3. 在 `release` 环境里人工批准后:给每个包签名,上传到 R2(`dl.shellrs.com/releases/<版本>/`),建 GitHub Release,**最后**才替换更新清单 `dl.shellrs.com/update/v1/<通道>.json` 并清 CDN 缓存,然后调用官网的部署挂钩重新构建 shellrs.com(官网的下载页和更新日志在构建时读这份清单)。清单里每个包有两个下载地址:先 R2,下载失败时客户端接着试 GitHub Release(仓库是公开的,下载不用登录)。
版本号带 `-` 的(如 `0.3.0-beta.1`)只发到 beta 通道;正式版同时成为 beta 通道的最新版。在 Actions 里手动运行这个 workflow 只构建打包、不发布,用来演练。客户端只接受比自己新的版本,所以发错的版本撤不回来:把清单改回上一版能阻止更多人升级,修复要发新的补丁版。
@@ -19,3 +19,5 @@
- **GitHub secrets**:仓库级的 `APPLE_CERTIFICATE_P12`(base64)、`APPLE_CERTIFICATE_PASSWORD`、`APPLE_SIGNING_IDENTITY`、`APPLE_TEAM_ID`、`APPLE_API_KEY_P8`(base64)、`APPLE_API_KEY_ID`、`APPLE_API_ISSUER`;`release` 环境(设为需要审批)里的 `MINISIGN_SECRET_KEY`、`MINISIGN_PASSWORD`、`R2_ACCOUNT_ID`、`R2_ACCESS_KEY_ID`、`R2_SECRET_ACCESS_KEY`、`R2_BUCKET`、`CF_ZONE_ID`、`CF_API_TOKEN`、`SITE_DEPLOY_HOOK`(官网部署挂钩的地址,拿到它的人能触发官网重新构建,泄露了就删掉重建)。
清单格式见 `packaging/manifest.example.json`,由 `packaging/make-manifest.sh` 生成:一个文件里是清单原文和它的 minisign 签名,签名的 trusted comment 写明通道和版本。格式只增加字段、不改已有的;已发出去的 ShellRS 一直按 `/update/v1/` 这个地址检查。
macOS 产物名分别为 `ShellRS-<版本>-macos-aarch64.{app.zip,dmg}` 和 `ShellRS-<版本>-macos-x86_64.{app.zip,dmg}`。更新清单保留原有的两个平台键,分别记录对应架构包的地址、大小和 SHA-256;已有 universal 客户端按当前运行的架构升级。Apple Silicon 上通过 Rosetta 运行 Intel 版时仍会收到 Intel 包,需要手动安装 ARM 版才能切换。官网在独立仓库中,下载页也需提供 Apple Silicon / Intel 两个入口,读取清单中各自的 installer 地址。
+12 -8
View File
@@ -23,8 +23,10 @@ out=$5
base="https://dl.shellrs.com/releases/$version"
mirror=${SHELLRS_MIRROR_BASE:-}
mac="ShellRS-$version-macos-universal.app.zip"
dmg="ShellRS-$version-macos-universal.dmg"
mac_arm="ShellRS-$version-macos-aarch64.app.zip"
mac_intel="ShellRS-$version-macos-x86_64.app.zip"
dmg_arm="ShellRS-$version-macos-aarch64.dmg"
dmg_intel="ShellRS-$version-macos-x86_64.dmg"
win="ShellRS-$version-windows-x86_64-setup.exe"
linux="ShellRS-$version-linux-x86_64.AppImage"
@@ -58,10 +60,12 @@ jq -n -j \
--arg published "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--rawfile notes "$notes" \
--arg notes_url "https://shellrs.com/changelog#$version" \
--argjson mac "$(asset "$mac")" \
--argjson mac_arm "$(asset "$mac_arm")" \
--argjson mac_intel "$(asset "$mac_intel")" \
--argjson win "$(asset "$win")" \
--argjson linux "$(asset "$linux")" \
--arg dmg "$base/$dmg" \
--arg dmg_arm "$base/$dmg_arm" \
--arg dmg_intel "$base/$dmg_intel" \
--arg win_url "$base/$win" \
--arg linux_url "$base/$linux" \
'{
@@ -74,14 +78,14 @@ jq -n -j \
notes: $notes,
notes_url: $notes_url,
assets: {
"macos-aarch64": $mac,
"macos-x86_64": $mac,
"macos-aarch64": $mac_arm,
"macos-x86_64": $mac_intel,
"windows-x86_64": $win,
"linux-x86_64": $linux
},
installers: {
"macos-aarch64": $dmg,
"macos-x86_64": $dmg,
"macos-aarch64": $dmg_arm,
"macos-x86_64": $dmg_intel,
"windows-x86_64": $win_url,
"linux-x86_64": $linux_url
}
+6 -6
View File
@@ -10,16 +10,16 @@
"assets": {
"macos-aarch64": {
"urls": [
"https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-universal.app.zip",
"https://github.com/since2006/shell-rs/releases/download/v0.2.0/ShellRS-0.2.0-macos-universal.app.zip"
"https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-aarch64.app.zip",
"https://github.com/since2006/shell-rs/releases/download/v0.2.0/ShellRS-0.2.0-macos-aarch64.app.zip"
],
"size": 41234567,
"sha256": "0000000000000000000000000000000000000000000000000000000000000000"
},
"macos-x86_64": {
"urls": [
"https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-universal.app.zip",
"https://github.com/since2006/shell-rs/releases/download/v0.2.0/ShellRS-0.2.0-macos-universal.app.zip"
"https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-x86_64.app.zip",
"https://github.com/since2006/shell-rs/releases/download/v0.2.0/ShellRS-0.2.0-macos-x86_64.app.zip"
],
"size": 41234567,
"sha256": "0000000000000000000000000000000000000000000000000000000000000000"
@@ -42,8 +42,8 @@
}
},
"installers": {
"macos-aarch64": "https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-universal.dmg",
"macos-x86_64": "https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-universal.dmg",
"macos-aarch64": "https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-aarch64.dmg",
"macos-x86_64": "https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-x86_64.dmg",
"windows-x86_64": "https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-windows-x86_64-setup.exe",
"linux-x86_64": "https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-linux-x86_64.AppImage"
}
+4 -5
View File
@@ -1,10 +1,9 @@
//! Which package of a release fits this machine.
//!
//! The keys are part of the manifest format, so they never change. A
//! universal macOS build runs its native half, so an Apple Silicon Mac asks
//! for `macos-aarch64` even though both keys point at the same package; an
//! Intel-only build under Rosetta would ask for `macos-x86_64` and keep
//! getting Intel builds, which is why macOS ships universal.
//! The keys are part of the manifest format, so they never change. macOS
//! packages match the running binary's architecture. Existing universal
//! builds select their native half's package; an Intel build under Rosetta
//! keeps receiving Intel packages until the user installs the ARM build.
/// The manifest's key for this build's platform.
pub fn platform_key() -> &'static str {
+31 -9
View File
@@ -319,7 +319,8 @@ pub(crate) mod tests {
let dist = dir.path().join("dist");
std::fs::create_dir(&dist).unwrap();
for name in [
"ShellRS-0.2.0-macos-universal.app.zip",
"ShellRS-0.2.0-macos-aarch64.app.zip",
"ShellRS-0.2.0-macos-x86_64.app.zip",
"ShellRS-0.2.0-windows-x86_64-setup.exe",
"ShellRS-0.2.0-linux-x86_64.AppImage",
] {
@@ -355,15 +356,36 @@ pub(crate) mod tests {
};
assert_eq!(manifest.version, Version::new(0, 2, 0));
assert_eq!(manifest.notes, "### 新增\n\n- 在线升级\n");
let mac = &manifest.assets["macos-aarch64"];
assert_eq!(mac, &manifest.assets["macos-x86_64"]);
// dl.shellrs.com first, the GitHub release only when it fails.
for arch in ["aarch64", "x86_64"] {
let key = format!("macos-{arch}");
let name = format!("ShellRS-0.2.0-{key}.app.zip");
let asset = &manifest.assets[&key];
// Each architecture has its own package and both download sources.
assert_eq!(
asset.urls,
[
format!("https://dl.shellrs.com/releases/0.2.0/{name}"),
format!(
"https://github.com/since2006/shell-rs/releases/download/v0.2.0/{name}"
),
]
);
assert_eq!(verify_file(&dist.join(&name), asset), Ok(()));
assert_eq!(
manifest.installers[&key],
format!("https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-{key}.dmg")
);
}
assert_ne!(
manifest.assets["macos-aarch64"].sha256,
manifest.assets["macos-x86_64"].sha256
);
assert_eq!(
mac.urls,
[
"https://dl.shellrs.com/releases/0.2.0/ShellRS-0.2.0-macos-universal.app.zip",
"https://github.com/since2006/shell-rs/releases/download/v0.2.0/ShellRS-0.2.0-macos-universal.app.zip",
]
verify_file(
&dist.join("ShellRS-0.2.0-macos-x86_64.app.zip"),
&manifest.assets["macos-aarch64"],
),
Err(UpdateError::Corrupt)
);
let package = dist.join("ShellRS-0.2.0-linux-x86_64.AppImage");
assert_eq!(