Files

183 lines
7.5 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 发布一个可分发的版本:Apple 芯片版与 Intel 版各自 Developer ID 签名 → 公证 → 装订 → DMG(签名、公证、装订)
# → 在线升级包 → 版本清单 → Homebrew cask。
#
# ./Scripts/release.sh
#
# 需要钥匙串里的 Developer ID Application 证书和 notarytool 凭据。凭据按 Apple ID 与团队保存,
# 默认用 GiantAccel 开发者账号的 “GiantAccel” 凭据;也可以单独保存一份:
# xcrun notarytool store-credentials OpenStats --apple-id you@example.com --team-id <TEAM_ID>
# NOTARY_PROFILE=OpenStats ./Scripts/release.sh
#
# SKIP_NOTARIZE=1 只生成未公证的 DMG 供本机测试——不要分发,别的 Mac 上 Gatekeeper 会拒绝打开。
set -euo pipefail
cd "$(dirname "$0")/.."
# 安装包放在官网
DOWNLOAD_BASE="${DOWNLOAD_BASE:-https://getopenstats.com/download}"
DIST="${DIST:-dist}"
NOTARY_PROFILE="${NOTARY_PROFILE:-GiantAccel}"
SIGN_ID="${SIGN_ID:-$(security find-identity -v -p codesigning 2>/dev/null \
| grep 'Developer ID Application' | head -1 | sed -E 's/.*"(.*)".*/\1/' || true)}"
VERSION="$(sed -nE 's/^ *MARKETING_VERSION: *"?([0-9.]+)"?.*/\1/p' project.yml | head -1)"
# 编译架构与文件名里的芯片名:OpenStats-0.3.1-AppleSilicon.dmg、OpenStats-0.3.1-Intel.dmg
ARCHS=(arm64 x86_64)
chip() { [ "$1" = arm64 ] && echo AppleSilicon || echo Intel; }
app_for() { echo "build/DerivedData-$1/Build/Products/Release/OpenStats.app"; }
# 在线升级的更新摘要取自该版本的更新日志:发版前把 “## 未发布” 改成 “## 版本 · 日期”
grep -q "^## ${VERSION} · " CHANGELOG.md \
|| { echo "error: CHANGELOG.md 里没有 “## ${VERSION} · 日期” 标题,先把 “## 未发布” 改成正式版本。" >&2; exit 1; }
if [ -z "$SIGN_ID" ]; then
echo "error: 钥匙串里没有 Developer ID Application 证书,无法发布。" >&2
exit 1
fi
TEAM_ID="$(echo "$SIGN_ID" | sed -nE 's/.*\(([A-Z0-9]+)\)$/\1/p')"
echo "版本 ${VERSION} · 签名身份:${SIGN_ID}"
notarize() {
echo "提交公证:$(basename "$1")(通常需要几分钟)…"
xcrun notarytool submit "$1" --keychain-profile "$NOTARY_PROFILE" --wait
}
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
rm -rf "$DIST"
mkdir -p "$DIST"
# 两种芯片共用一个构建号:先加一次,各自构建时不再加
./Scripts/version.sh build >/dev/null
BUILD="$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: *"?([0-9]+)"?.*/\1/p' project.yml | head -1)"
for arch in "${ARCHS[@]}"; do
APP="$(app_for "$arch")"
NAME="OpenStats-${VERSION}-$(chip "$arch")"
echo
echo "==== $(chip "$arch")(${arch})===="
# ---- 构建 ------------------------------------------------------------------
rm -rf "$APP"
make build CONFIG=Release INSTALL=0 BUMP=0 ARCH="$arch" SIGN_ID="$SIGN_ID"
for binary in "$APP/Contents/MacOS/OpenStats" "$APP/Contents/MacOS/OpenStatsHelper" \
"$APP/Contents/PlugIns/OpenStatsWidget.appex/Contents/MacOS/OpenStatsWidget"; do
[ "$(lipo -archs "$binary")" = "$arch" ] \
|| { echo "error: $binary 的架构是 $(lipo -archs "$binary"),应当只有 $arch" >&2; exit 1; }
done
codesign --verify --deep --strict --verbose=2 "$APP"
for binary in "$APP" "$APP/Contents/MacOS/OpenStatsHelper" "$APP/Contents/PlugIns/OpenStatsWidget.appex"; do
details="$(codesign -dvv "$binary" 2>&1)"
echo "$details" | grep -q "TeamIdentifier=${TEAM_ID}" \
|| { echo "error: $binary 未使用团队 ${TEAM_ID} 签名" >&2; exit 1; }
echo "$details" | grep -q "Timestamp=" \
|| { echo "error: $binary 缺少安全时间戳" >&2; exit 1; }
echo "$details" | grep -Eq "flags=.*runtime" \
|| { echo "error: $binary 未启用 Hardened Runtime" >&2; exit 1; }
done
# ---- 公证 App --------------------------------------------------------------
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
# ditto 而不是 zip:保留包内的符号链接与扩展属性
ditto -c -k --keepParent "$APP" "$WORK/$NAME-notarize.zip"
notarize "$WORK/$NAME-notarize.zip"
xcrun stapler staple "$APP"
xcrun stapler validate "$APP"
# 拒绝发布 Gatekeeper 在用户机器上仍会拒绝的包(Intel 版的签名与公证票据同样能在本机校验)
if ! spctl -a -vv "$APP" 2>&1 | grep -q accepted; then
echo "error: Gatekeeper 仍然拒绝该 App,停止发布。" >&2
spctl -a -vv "$APP" || true
exit 1
fi
fi
# ---- DMG -------------------------------------------------------------------
rm -rf "$WORK/dmg"
mkdir -p "$WORK/dmg"
ditto "$APP" "$WORK/dmg/OpenStats.app"
ln -s /Applications "$WORK/dmg/Applications"
hdiutil create -volname "OpenStats ${VERSION}" -srcfolder "$WORK/dmg" -ov -format UDZO "$DIST/$NAME.dmg" >/dev/null
codesign --force --timestamp --sign "$SIGN_ID" "$DIST/$NAME.dmg"
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
notarize "$DIST/$NAME.dmg"
xcrun stapler staple "$DIST/$NAME.dmg"
spctl -a -t open --context context:primary-signature -vv "$DIST/$NAME.dmg"
fi
# ---- 在线升级 ----------------------------------------------------------------
# 应用内升级下载已装订票据的 .app 压缩包
ditto -c -k --keepParent "$APP" "$DIST/$NAME.zip"
done
# 版本清单:顶层是 Apple 芯片版(0.3.0 只认顶层字段),intel 字段是 Intel 版
python3 Scripts/appcast.py "$VERSION" "$BUILD" "$DOWNLOAD_BASE" \
"$DIST/OpenStats-${VERSION}-AppleSilicon.zip" "$DIST/OpenStats-${VERSION}-AppleSilicon.dmg" \
"$DIST/OpenStats-${VERSION}-Intel.zip" "$DIST/OpenStats-${VERSION}-Intel.dmg" > "$DIST/appcast.json"
# ---- Homebrew cask ------------------------------------------------------------
SHA_ARM="$(shasum -a 256 "$DIST/OpenStats-${VERSION}-AppleSilicon.dmg" | cut -d' ' -f1)"
SHA_INTEL="$(shasum -a 256 "$DIST/OpenStats-${VERSION}-Intel.dmg" | cut -d' ' -f1)"
cat > "$DIST/openstats.rb" <<CASK
cask "openstats" do
arch arm: "AppleSilicon", intel: "Intel"
version "${VERSION}"
sha256 arm: "${SHA_ARM}",
intel: "${SHA_INTEL}"
url "${DOWNLOAD_BASE}/OpenStats-#{version}-#{arch}.dmg"
name "OpenStats"
desc "Menu bar system monitor with fan control, keep-awake and cleanup"
homepage "https://getopenstats.com/"
# 新版本以官网的在线升级清单为准
livecheck do
url "${DOWNLOAD_BASE}/appcast.json"
strategy :json do |json|
json["version"]
end
end
# 应用内置在线升级,brew upgrade 默认不再重复升级
auto_updates true
depends_on macos: :sonoma
app "OpenStats.app"
# 卸载前退出应用,并停掉可选的特权辅助工具(没装过就跳过)
uninstall launchctl: "com.openstats.helper",
quit: "com.openstats.app"
zap trash: [
"~/Library/Application Support/OpenStats",
"~/Library/Caches/com.openstats.app",
"~/Library/Containers/com.openstats.app.widget",
"~/Library/Logs/OpenStats",
"~/Library/Preferences/com.openstats.app.plist",
]
end
CASK
# 本机只保留一份:把本机芯片的发布版装到 /Applications,另一种芯片的编译产物由安装脚本一并清掉
native="$(uname -m)"
./Scripts/install_local.sh "$(app_for "$native")"
echo
for arch in "${ARCHS[@]}"; do
NAME="OpenStats-${VERSION}-$(chip "$arch")"
echo "✅ ${DIST}/${NAME}.dmg SHA-256 $(shasum -a 256 "$DIST/$NAME.dmg" | cut -d' ' -f1)"
done
echo " 在线升级:${DIST}/*.zip · ${DIST}/appcast.json"
echo " Homebrew cask:${DIST}/openstats.rb"
if [ "${SKIP_NOTARIZE:-0}" = "1" ]; then
echo "⚠️ 未公证,仅供本机测试。"
else
echo "下一步:./Scripts/publish_release.sh 上传安装包到官网并更新 gentpan/homebrew-tap。"
fi