Files
OpenStats/Scripts/release.sh
T
gentpan e2300826b6 发布 0.3.0:更新 README 截图,补充卸载应用与启动项说明,自动检查更新失败后按小时重试
- 官网此前的 0.2.0 安装包早于卸载应用、启动项与在线升级的提交,发布 0.3.0 补上(#1)
- 自动检查更新只有拿到版本清单才记为已检查,登录时网络未就绪导致的失败不再等 24 小时才重试
- release.sh 在构建后读取构建号,版本清单里的 build 与安装包一致
- README 截图换成 0.3.0 界面,新增启动项截图与“卸载应用与启动项”一节
2026-09-15 14:43:07 +05:00

139 lines
5.6 KiB
Bash
Executable File
Raw 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
# 发布一个可分发的版本:Developer ID 签名 → 公证 → 装订 → DMG(签名、公证、装订)→ 在线升级包与版本清单 → Homebrew cask。
#
# ./Scripts/release.sh
#
# 需要钥匙串里的 Developer ID Application 证书和 notarytool 凭据。凭据按 Apple ID 与团队保存,
# 默认沿用 QuotaBar 的 “QuotaBar” 凭据;也可以单独保存一份:
# 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")/.."
# 安装包放在官网(仓库是私有的,GitHub Release 无法匿名下载)
DOWNLOAD_BASE="${DOWNLOAD_BASE:-https://getopenstats.com/download}"
DIST="${DIST:-dist}"
NOTARY_PROFILE="${NOTARY_PROFILE:-QuotaBar}"
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)"
APP="build/DerivedData/Build/Products/Release/OpenStats.app"
DMG_NAME="OpenStats-${VERSION}.dmg"
ZIP_NAME="OpenStats-${VERSION}.zip"
# 在线升级的更新摘要取自该版本的更新日志:发版前把 “## 未发布” 改成 “## 版本 · 日期”
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}"
# ---- 构建 --------------------------------------------------------------------
make build CONFIG=Release INSTALL=0 SIGN_ID="$SIGN_ID"
# make build 会把构建号加一,构建完再读,版本清单里的 build 才与安装包一致
BUILD="$(sed -nE 's/^ *CURRENT_PROJECT_VERSION: *"?([0-9]+)"?.*/\1/p' project.yml | head -1)"
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
notarize() {
echo "提交公证:$(basename "$1")(通常需要几分钟)…"
xcrun notarytool submit "$1" --keychain-profile "$NOTARY_PROFILE" --wait
}
# ---- 公证 App ----------------------------------------------------------------
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
# ditto 而不是 zip:保留包内的符号链接与扩展属性
ditto -c -k --keepParent "$APP" "$WORK/OpenStats.zip"
notarize "$WORK/OpenStats.zip"
xcrun stapler staple "$APP"
xcrun stapler validate "$APP"
# 拒绝发布 Gatekeeper 在用户机器上仍会拒绝的包
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 "$DIST"
mkdir -p "$DIST" "$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/$DMG_NAME" >/dev/null
codesign --force --timestamp --sign "$SIGN_ID" "$DIST/$DMG_NAME"
if [ "${SKIP_NOTARIZE:-0}" != "1" ]; then
notarize "$DIST/$DMG_NAME"
xcrun stapler staple "$DIST/$DMG_NAME"
spctl -a -t open --context context:primary-signature -vv "$DIST/$DMG_NAME"
fi
SHA256="$(shasum -a 256 "$DIST/$DMG_NAME" | cut -d' ' -f1)"
# ---- 在线升级 ------------------------------------------------------------------
# 应用内升级下载已装订票据的 .app 压缩包,版本清单里带 sha256 与更新摘要
ditto -c -k --keepParent "$APP" "$DIST/$ZIP_NAME"
python3 Scripts/appcast.py "$VERSION" "$BUILD" "$DIST/$ZIP_NAME" "$DIST/$DMG_NAME" "$DOWNLOAD_BASE" > "$DIST/appcast.json"
# ---- Homebrew cask ------------------------------------------------------------
cat > "$DIST/openstats.rb" <<CASK
cask "openstats" do
version "${VERSION}"
sha256 "${SHA256}"
url "${DOWNLOAD_BASE}/OpenStats-#{version}.dmg"
name "OpenStats"
desc "Menu bar system monitor with fan control, keep-awake and cleanup"
homepage "https://getopenstats.com"
# 应用内置在线升级,brew upgrade 默认不再重复升级
auto_updates true
depends_on macos: :sonoma
app "OpenStats.app"
zap trash: [
"~/Library/Logs/OpenStats",
"~/Library/Preferences/com.openstats.app.plist",
]
end
CASK
# 本机只保留一份:把刚公证的发布版装到 /Applications,编译目录里不留副本
./Scripts/install_local.sh "$APP"
echo
echo "✅ ${DIST}/${DMG_NAME}"
echo " SHA-256 ${SHA256}"
echo " 在线升级:${DIST}/${ZIP_NAME} · ${DIST}/appcast.json"
echo " Homebrew cask:${DIST}/openstats.rb"
if [ "${SKIP_NOTARIZE:-0}" = "1" ]; then
echo "⚠️ 未公证,仅供本机测试。"
else
echo "下一步:./Scripts/publish_release.sh 上传 DMG 到官网并更新 gentpan/homebrew-tap。"
fi