Files
openless/scripts/release.sh
T
TRIPandbaiqing f38643f17c feat: Sparkle 2 auto-update
* feat: integrate Sparkle 2 for auto-update

Adds the standard pieces for Sparkle-based silent updates:

- Package.swift: add Sparkle 2 SPM dep, wired into OpenLessApp.
- Sources/OpenLessApp/Updater/UpdaterController.swift: thin wrapper
  around SPUStandardUpdaterController. Owned by AppDelegate so the
  updater starts on app launch.
- AppDelegate: instantiate UpdaterController before installing the
  main menu; pass it to ApplicationMenu so the menu item can target
  it.
- ApplicationMenu: add "检查更新…" as the first item in the OpenLess
  menu when an UpdaterController target is present.
- scripts/build-app.sh: copy Sparkle.framework (with Updater.app +
  XPC services + Autoupdate) from the SPM artifacts dir into
  Contents/Frameworks/, inject SUFeedURL / SUPublicEDKey /
  SUEnableAutomaticChecks / SUEnableInstallerLauncherService /
  SUScheduledCheckInterval into Info.plist, and ad-hoc sign the
  embedded helpers explicitly (deep sign alone is unreliable for
  Sparkle's nested XPC bundles).
- scripts/release.sh: one-shot release pipeline.
  ./scripts/release.sh <version> [<build>] [<notes>]
  Bumps version in build-app.sh, builds, zips, signs the zip with the
  Keychain-stored EdDSA private key (auto-downloads sign_update from
  the Sparkle 2.9.1 release on first run), prepends a new <item> to
  appcast.xml, commits + tags + pushes main, and creates the GitHub
  release with the zip uploaded.
- appcast.xml: empty initial feed file at repo root, served via the
  raw.githubusercontent.com URL referenced by SUFeedURL. Items are
  appended automatically by release.sh.

Public EdDSA key embedded in the build:
iT00+eUw/55obn1suEnWqI7za2pc8mHIFIdRbOWXW1Q=

Private key lives only in the local Keychain on the release machine
(account: macOS login user; service: https://sparkle-project.org).
First-time install still requires the user to remove the quarantine
xattr (no Apple Developer ID); subsequent updates flow through
Sparkle's installer helper and bypass quarantine.

* fix(sparkle): add @executable_path/../Frameworks rpath to OpenLessApp

Without this, the SPM-built executable links Sparkle's symbols but
ships with no rpath that points at the embedded framework, so dyld
fails at launch with:

  Library not loaded: @rpath/Sparkle.framework/Versions/B/Sparkle

…and the user sees the macOS Gatekeeper-styled "请与开发者联系,以确保
OpenLess 可以配合此 macOS 版本使用" error. Inject the rpath via
linkerSettings.unsafeFlags so the binary itself knows where to look.

---------

Co-authored-by: baiqing <lbx12309@icloud.com>
2026-04-27 18:58:37 +08:00

143 lines
4.8 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.
#!/usr/bin/env bash
# 发新版的一条龙脚本。
#
# 用法:
# ./scripts/release.sh <version> [<build_number>] [<release_notes_file>]
#
# 例:
# ./scripts/release.sh 1.0.02 A1004 docs/release-notes/1.0.02.md
# ./scripts/release.sh 1.0.02
#
# 流程:
# 1. 改 build-app.sh 里的 APP_VERSION / BUILD_NUMBER
# 2. 跑 build-app.sh 出 .app
# 3. ditto 打包成 OpenLess-<version>.zip
# 4. sign_update zip → 拿 EdDSA 签名 + 长度
# 5. 在 appcast.xml 里追加新 <item>
# 6. git commit appcast.xml + build-app.sh
# 7. git tag v<version> + push main + push tag
# 8. gh release create + 上传 zip
#
# 依赖:
# - /tmp/sparkle-tools/bin/sign_update(首次会自动从 Sparkle release 下载)
# - gh CLI 已登录
# - Keychain 里有 Sparkle EdDSA 私钥(已生成一次性)
set -euo pipefail
cd "$(dirname "$0")/.."
VERSION="${1:?用法: ./scripts/release.sh <version> [<build_number>] [<release_notes_file>]}"
BUILD="${2:-}"
NOTES_FILE="${3:-}"
if [ -z "${BUILD}" ]; then
# 默认把 build number 设为 version 的简单变形(去掉点)。用户可显式覆盖。
BUILD="$(echo "${VERSION}" | tr -d '.')"
fi
APP_NAME="OpenLess"
APP_DIR="build/${APP_NAME}.app"
ZIP_NAME="${APP_NAME}-${VERSION}.zip"
ZIP_PATH="build/${ZIP_NAME}"
APPCAST="appcast.xml"
SPARKLE_TOOLS_DIR="/tmp/sparkle-tools"
SIGN_UPDATE="${SPARKLE_TOOLS_DIR}/bin/sign_update"
SPARKLE_VERSION="2.9.1"
# 1. 确保 sign_update 工具就位
if [ ! -x "${SIGN_UPDATE}" ]; then
echo "[release] 下载 Sparkle ${SPARKLE_VERSION} 工具..."
mkdir -p "${SPARKLE_TOOLS_DIR}"
(cd "${SPARKLE_TOOLS_DIR}" && \
gh release download "${SPARKLE_VERSION}" -R sparkle-project/Sparkle \
-p "Sparkle-${SPARKLE_VERSION}.tar.xz" --skip-existing && \
tar -xJf "Sparkle-${SPARKLE_VERSION}.tar.xz")
fi
# 2. 改 build-app.sh 里的版本
echo "[release] 写入版本号 ${VERSION} / ${BUILD}"
/usr/bin/sed -i '' "s/^APP_VERSION=.*/APP_VERSION=\"${VERSION}\"/" scripts/build-app.sh
/usr/bin/sed -i '' "s/^BUILD_NUMBER=.*/BUILD_NUMBER=\"${BUILD}\"/" scripts/build-app.sh
# 3. 构建 .app(不重置 TCC,避免本机权限被清掉)
echo "[release] 构建 .app"
RESET_TCC=0 ./scripts/build-app.sh > /dev/null
# 4. 打包 zip
rm -f "${ZIP_PATH}"
ditto -c -k --keepParent "${APP_DIR}" "${ZIP_PATH}"
ZIP_SIZE=$(/usr/bin/stat -f%z "${ZIP_PATH}")
echo "[release] zip = ${ZIP_PATH} (${ZIP_SIZE} bytes)"
# 5. 用 Keychain 里的 EdDSA 私钥签 zip
SIGN_OUTPUT=$("${SIGN_UPDATE}" "${ZIP_PATH}")
# 输出形如:sparkle:edSignature="xxxx" length="1234567"
EDSIG=$(echo "${SIGN_OUTPUT}" | /usr/bin/sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p')
[ -n "${EDSIG}" ] || { echo "签名失败,sign_update 输出:${SIGN_OUTPUT}"; exit 1; }
echo "[release] EdDSA 签名 OK"
# 6. 准备 release notes(HTML 片段)
if [ -n "${NOTES_FILE}" ] && [ -f "${NOTES_FILE}" ]; then
NOTES_HTML="<![CDATA[$(cat "${NOTES_FILE}")]]>"
else
NOTES_HTML="<![CDATA[<p>OpenLess ${VERSION}</p>]]>"
fi
PUB_DATE=$(/bin/date -u +"%a, %d %b %Y %H:%M:%S +0000")
DOWNLOAD_URL="https://github.com/appergb/openless/releases/download/v${VERSION}/${ZIP_NAME}"
# 7. 在 appcast.xml 的 <channel> 顶部插入新 <item>
NEW_ITEM=$(cat <<EOF
<item>
<title>OpenLess ${VERSION}</title>
<pubDate>${PUB_DATE}</pubDate>
<sparkle:version>${BUILD}</sparkle:version>
<sparkle:shortVersionString>${VERSION}</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description>${NOTES_HTML}</description>
<enclosure
url="${DOWNLOAD_URL}"
length="${ZIP_SIZE}"
type="application/octet-stream"
sparkle:edSignature="${EDSIG}" />
</item>
EOF
)
# 用 awk 在 <description>OpenLess update feed</description> 之后插入新 item
TMP_APPCAST=$(/usr/bin/mktemp)
/usr/bin/awk -v new_item="${NEW_ITEM}" '
/<description>OpenLess update feed<\/description>/ {
print
print ""
print new_item
next
}
{ print }
' "${APPCAST}" > "${TMP_APPCAST}"
mv "${TMP_APPCAST}" "${APPCAST}"
echo "[release] appcast.xml 已追加 ${VERSION}"
# 8. git commit + tag + push
git add scripts/build-app.sh "${APPCAST}"
git commit -m "release: v${VERSION} (build ${BUILD})"
git tag "v${VERSION}"
git push origin main
git push origin "v${VERSION}"
# 9. gh release create + 上传 zip
gh release create "v${VERSION}" "${ZIP_PATH}" \
--title "OpenLess ${VERSION}" \
--notes-file "${NOTES_FILE:-/dev/stdin}" <<EOF
OpenLess ${VERSION} (build ${BUILD}).
老用户:app 启动后自动检查更新;首次安装见 README。
EOF
echo
echo "[release] 完成 ✅"
echo " Tag: v${VERSION}"
echo " Release: https://github.com/appergb/openless/releases/tag/v${VERSION}"
echo " Zip: ${ZIP_PATH} (${ZIP_SIZE} bytes)"
echo " Sig: ${EDSIG}"