From e700b827e27bbb013b40c92a2b954c9690e4cf3e Mon Sep 17 00:00:00 2001 From: tmoonlight Date: Fri, 16 Oct 2026 08:27:24 +0800 Subject: [PATCH 1/2] Stop shipping Deepin ARMv7 and retry Apple ticket disconnects v1.0.7's tagged build lost two platforms. Deepin ARMv7 could not install its cross toolchain because archive.debian.org kept serving cached 404s; it is no longer published, so the job, its toolchain and ARMv7 checks are removed and a stray archive now fails release verification. The release set drops to 17 required downloads. macos-x64 failed three runs in a row because xcrun stapler could not reach Apple's ticket service (exit 68, CloudKit -1005). Every stapler call in the macOS packaging scripts now goes through acecode_stapler, which retries only exit 68 (six attempts, 15 seconds apart) and fails other errors at once. Contract tests cover recovery, persistent failure, non-68 errors and reject new direct xcrun stapler calls. --- .github/workflows/package.yml | 58 +------------------ docs/deepin-desktop.md | 4 +- docs/help-source/getting-started.json | 2 +- docs/help/assets/search-index.js | 4 +- docs/help/installation.html | 2 +- docs/macos-release.md | 9 +++ docs/release-artifacts.md | 6 +- .../.openspec.yaml | 2 + .../proposal.md | 31 ++++++++++ .../specs/deepin-release-packaging/spec.md | 16 +++++ .../specs/macos-stapler-retry/spec.md | 23 ++++++++ .../tasks.md | 18 ++++++ packaging/macos-installer/build.sh | 4 +- scripts/macos_create_installer_dmg.sh | 8 ++- scripts/macos_create_update_zip.sh | 4 +- scripts/macos_notarize_app.sh | 6 +- scripts/macos_notarize_pkg.sh | 6 +- scripts/macos_stapler.sh | 34 +++++++++++ scripts/verify_release_assets.py | 5 +- tests/scripts/macos_installer_release_test.py | 48 ++++++++++++++- tests/scripts/macos_release_scripts_test.sh | 42 ++++++++++++++ tests/scripts/verify_release_assets_test.py | 20 +++++-- 22 files changed, 270 insertions(+), 82 deletions(-) create mode 100644 openspec/changes/retire-deepin-armv7-and-retry-stapler/.openspec.yaml create mode 100644 openspec/changes/retire-deepin-armv7-and-retry-stapler/proposal.md create mode 100644 openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/deepin-release-packaging/spec.md create mode 100644 openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/macos-stapler-retry/spec.md create mode 100644 openspec/changes/retire-deepin-armv7-and-retry-stapler/tasks.md create mode 100644 scripts/macos_stapler.sh diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 776b7cacb..166b7222c 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -944,23 +944,6 @@ jobs: -DACECODE_BUILD_DESKTOP=ON -DACECODE_DEEPIN=ON -DWEBVIEW_WEBKITGTK_API=4.0 - - id: linux-deepin-armv7 - os: ubuntu-22.04 - triplet: arm-linux - executable: acecode - archive_extension: tar.gz - build_desktop: false - cmake_extra_args: >- - -DACECODE_BUILD_DESKTOP=OFF - -DACECODE_DEEPIN=ON - -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/toolchains/linux.cmake" - -DVCPKG_TARGET_ARCHITECTURE=arm - -DCMAKE_C_COMPILER=clang-11 - -DCMAKE_CXX_COMPILER=clang++-11 - -DCMAKE_C_COMPILER_TARGET=arm-linux-gnueabihf - -DCMAKE_CXX_COMPILER_TARGET=arm-linux-gnueabihf - -DCMAKE_C_STANDARD_INCLUDE_DIRECTORIES=/usr/arm-linux-gnueabihf/include - -DCMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES="/usr/arm-linux-gnueabihf/include/c++/8;/usr/arm-linux-gnueabihf/include/c++/8/arm-linux-gnueabihf;/usr/arm-linux-gnueabihf/include" steps: - name: Checkout @@ -1003,15 +986,6 @@ jobs: libegl1-mesa-dev libudev-dev libxi-dev libcups2-dev librsvg2-dev ) fi - if [ "${{ matrix.id }}" = "linux-deepin-armv7" ]; then - packages+=( - binutils-arm-linux-gnueabihf - clang-11 - gcc-arm-linux-gnueabihf - g++-arm-linux-gnueabihf - qemu-user - ) - fi apt-get install -y --no-install-recommends "${packages[@]}" rm -rf /var/lib/apt/lists/* @@ -1106,40 +1080,14 @@ jobs: fi fi - - name: Verify Deepin ARMv7 executable - if: matrix.id == 'linux-deepin-armv7' - shell: bash - run: | - export LC_ALL=C - elf_header="$(arm-linux-gnueabihf-readelf -h build/acecode)" - arm_attributes="$(arm-linux-gnueabihf-readelf -A build/acecode)" - program_headers="$(arm-linux-gnueabihf-readelf -l build/acecode)" - - printf '%s\n' "$elf_header" - printf '%s\n' "$arm_attributes" - - grep -Eq 'Class:[[:space:]]+ELF32' <<<"$elf_header" - grep -Eq 'Machine:[[:space:]]+ARM' <<<"$elf_header" - grep -Eq 'Tag_CPU_arch:[[:space:]]+v7' <<<"$arm_attributes" - grep -Eq 'Tag_ABI_VFP_args:[[:space:]]+VFP registers' <<<"$arm_attributes" - grep -Fq '/lib/ld-linux-armhf.so.3' <<<"$program_headers" - - version_output="$(qemu-arm -L /usr/arm-linux-gnueabihf build/acecode --version)" - printf '%s\n' "$version_output" - grep -Eq '^acecode v[^[:space:]]+$' <<<"$version_output" - - name: Extract debug symbols shell: bash run: | - binutils_prefix="" - if [ "${{ matrix.id }}" = "linux-deepin-armv7" ]; then - binutils_prefix="arm-linux-gnueabihf-" - fi - "${binutils_prefix}objcopy" --only-keep-debug \ + objcopy --only-keep-debug \ "build/${{ matrix.executable }}" \ "build/${{ matrix.executable }}.debug" - "${binutils_prefix}strip" --strip-unneeded "build/${{ matrix.executable }}" - "${binutils_prefix}objcopy" \ + strip --strip-unneeded "build/${{ matrix.executable }}" + objcopy \ --add-gnu-debuglink="build/${{ matrix.executable }}.debug" \ "build/${{ matrix.executable }}" diff --git a/docs/deepin-desktop.md b/docs/deepin-desktop.md index 4e79a5f4d..c96f256f1 100644 --- a/docs/deepin-desktop.md +++ b/docs/deepin-desktop.md @@ -17,7 +17,7 @@ Deepin 包使用系统 WebKit2GTK **4.0**、Qt **5.11 或更新的 Qt 5**、DTK ## 运行条件 - x64、ARM64:归档包含 `acecode` 和 `acecode-desktop`。 -- ARMv7:延续原流水线的终端/daemon 包,不包含 Desktop。 +- ARMv7:1.0.7 是最后一个 Deepin ARMv7 包(终端/daemon,不含 Desktop)。已下载的版本仍可使用,但不会再有新版本。普通 `acecode-linux-armv7.tar.gz` 按较新的 glibc 构建,不保证能在 GLIBC 2.28 的旧 UOS / Deepin 上运行。 - 桌面依赖系统 `libwebkit2gtk-4.0.so.37`、`libdtkwidget.so.5`、`libdtkgui.so.5`、Qt 5 及 Deepin `dxcb` 平台插件。使用系统随附的对应运行库,归档不捆绑 Qt、DTK 或 WebKit。 - 原生窗框效果还要求 `XDG_CURRENT_DESKTOP` 或 `XDG_SESSION_DESKTOP` 含有 `Deepin` 桌面名称,并且窗口实际使用 GDK X11 后端。XWayland 满足这一后端条件,CPU 架构不参与判断。原生 Wayland 跳过 X11 窗框适配。 - Deepin 的 DPI 修正也受编译开关和上述运行条件限制;有效字体 DPI 大于 96 且缩放参数有效时启用,显示设置改变后实时刷新。 @@ -26,7 +26,7 @@ Deepin 包使用系统 WebKit2GTK **4.0**、Qt **5.11 或更新的 Qt 5**、DTK ## 打包与升级 -下载文件为 `acecode-linux-deepin-x64.tar.gz`、`acecode-linux-deepin-arm64.tar.gz` 和 `acecode-linux-deepin-armv7.tar.gz`,替代原 `linux-old-*` 系列。普通 `acecode-linux-*` 包继续单独发布。 +下载文件为 `acecode-linux-deepin-x64.tar.gz` 和 `acecode-linux-deepin-arm64.tar.gz`,替代原 `linux-old-*` 系列。普通 `acecode-linux-*` 包继续单独发布。 专用流水线保持 Debian Buster / GLIBC 2.28 基线。`scripts/build_deepin_dtk_sdk.sh` 从固定提交及 SHA-256 校验的源码构建 DTK 5.2 SDK,避免系统仓库更新 Qt 后抬高 ABI 要求。SDK 只用于 CI 链接,不放进发行包。流水线校验 GLIBC 上限、WebKitGTK 4.0、DTK 5 和动态依赖是否齐全。打包前及解包后运行 `scripts/verify_deepin_package.py`,拒绝归档中的 Qt/DTK 运行库和 Qt 插件。 diff --git a/docs/help-source/getting-started.json b/docs/help-source/getting-started.json index 3284b1bc6..7963342a1 100644 --- a/docs/help-source/getting-started.json +++ b/docs/help-source/getting-started.json @@ -45,7 +45,7 @@ { "id": "linux", "title": "Linux", - "html": "

Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz。下面以 x64 压缩包为例,在下载目录执行:

\n
Shell · 从压缩包启动
tar -xzf acecode-linux-x64.tar.gz\ncd acecode-linux-x64\n./acecode configure\n./acecode
\n

ARM64 用户替换文件名和目录名中的 x64 为 arm64。直接解压运行时使用 ./acecode;将程序目录加入 PATH 后,才可在其他目录使用 acecode。

\n

使用 Linux 桌面端

桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动:

\n
Shell · 桌面运行库与启动
sudo apt install libwebkit2gtk-4.1-0\n./acecode-desktop
\n

其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包;桌面端使用系统 WebKitGTK 4.0 和 DTK 5,终端 ARMv7 包不包含桌面端。

\n

通过浏览器使用

安装终端版后,也可以启动后台服务。npm 安装时执行:

\n
终端 · 启动 Web 服务
acecode daemon
\n

未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399。便携版请在程序目录执行 ./acecode daemon。配置了端口或认证时,按服务启动信息访问。

\n \n

请优先打开启动输出中的 Web UI: 地址;已保存的端口配置可能与默认值不同。完整操作见Web。

" + "html": "

Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz。下面以 x64 压缩包为例,在下载目录执行:

\n
Shell · 从压缩包启动
tar -xzf acecode-linux-x64.tar.gz\ncd acecode-linux-x64\n./acecode configure\n./acecode
\n

ARM64 用户替换文件名和目录名中的 x64 为 arm64。直接解压运行时使用 ./acecode;将程序目录加入 PATH 后,才可在其他目录使用 acecode。

\n

使用 Linux 桌面端

桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动:

\n
Shell · 桌面运行库与启动
sudo apt install libwebkit2gtk-4.1-0\n./acecode-desktop
\n

其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包(x64、arm64);桌面端使用系统 WebKitGTK 4.0 和 DTK 5。1.0.7 是最后一个 Deepin ARMv7 版本。

\n

通过浏览器使用

安装终端版后,也可以启动后台服务。npm 安装时执行:

\n
终端 · 启动 Web 服务
acecode daemon
\n

未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399。便携版请在程序目录执行 ./acecode daemon。配置了端口或认证时,按服务启动信息访问。

\n \n

请优先打开启动输出中的 Web UI: 地址;已保存的端口配置可能与默认值不同。完整操作见Web。

" } ], "sources": [ diff --git a/docs/help/assets/search-index.js b/docs/help/assets/search-index.js index 1a11d104f..0357babaa 100644 --- a/docs/help/assets/search-index.js +++ b/docs/help/assets/search-index.js @@ -38,7 +38,7 @@ window.ACECODE_HELP_INDEX = [ "title": "安装", "url": "installation.html", "description": "选择适合系统和处理器的安装方式,确认程序能启动后,再配置第一个模型。", - "text": "安装前准备 先确定你使用的操作系统和处理器架构。Windows、macOS 和 Linux 的安装包不能混用;Apple 芯片与 Intel Mac 也需要选择不同的包。 模型访问方式: 准备服务商提供的 API Key,或使用支持的账号登录方式。安装完成后再配置即可。 开发环境: 若任务需要运行项目,请先准备项目依赖,例如 Git、Node.js、Python 或相应编译工具。 npm 安装: 需要电脑上已有 Node.js 和 npm。直接下载原生安装包时,不需要为了启动 ACECode 安装 Node.js。 所有手动下载入口都指向 ACECode 官方发布页 。在 Assets 中选择对应平台的产物,具体可用文件以该版本发布内容为准。 通过 npm 安装 如果你已经使用 npm,可以直接安装桌面端或终端版。安装时会按系统和处理器选择对应的原生程序。 桌面端 终端 · 安装并启动桌面端 复制 npm install -g @aceagent/desktop acecode-desktop 终端版 终端 · 安装、配置并启动 TUI 复制 npm install -g @aceagent/acecode acecode configure acecode 保留可选依赖 安装时请不要添加 --omit=optional 或 --no-optional ,平台所需的原生程序通过可选依赖安装。桌面端与 CLI 提供不同的启动命令,需要两个入口时可分别安装。 终端版与桌面端分别提供 acecode 与 acecode-desktop 命令。安装后重新打开终端,用 acecode --version 检查终端版是否可用。 Windows 桌面端可使用 Windows x64 安装器,也可以使用上方的 npm 方式。 打开官方发布页,找到该版本提供的 Windows 安装器,例如 ACECode-<版本>-windows-x64-setup.exe 。 双击安装器,按向导完成安装。默认安装到当前用户的 %LOCALAPPDATA%\\Programs\\ACECode 。 从开始菜单启动 ACECode,然后前往 配置第一个模型 。 Windows 安装向导的欢迎页和目标位置页。画面来自本机已有的 v0.9.6 安装包,仅展示向导流程,下载时请以发布页为准。本次查看后已退出向导,未执行安装。 Windows 实际界面 · 点击图片查看原图 Windows 桌面界面需要 Microsoft Edge WebView2 Runtime。若当前系统没有该运行时,请按安装提示补齐。使用便携包时,请保留解压后的完整目录,桌面程序需要同目录中的后台程序和资源文件。 macOS 先在苹果菜单的“关于本机”中确认芯片类型,再选择对应的安装包。 你的 Mac 选择的架构 Apple 芯片(M 系列) macos-arm64 Intel 处理器 macos-x64 若当前发布版本提供 .pkg ,下载与架构匹配的包并双击打开。 按照系统安装器提示完成当前用户安装。 在 ~/Applications/ACECode.app 打开 ACECode,继续配置模型。 GS-03 截图占位 macOS 安装与首次打开 此处将补充实际界面截图 16 : 9 macOS:展示 PKG 安装完成后,当前用户“应用程序”目录中的 ACECode。 若该版本没有 PKG,可使用 npm,或按发行说明使用对应架构的压缩包。ACECode 的 macOS 发布产物不使用 DMG 安装流程。 Linux Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz 。下面以 x64 压缩包为例,在下载目录执行: Shell · 从压缩包启动 复制 tar -xzf acecode-linux-x64.tar.gz cd acecode-linux-x64 ./acecode configure ./acecode ARM64 用户替换文件名和目录名中的 x64 为 arm64 。直接解压运行时使用 ./acecode ;将程序目录加入 PATH 后,才可在其他目录使用 acecode 。 使用 Linux 桌面端 桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动: Shell · 桌面运行库与启动 复制 sudo apt install libwebkit2gtk-4.1-0 ./acecode-desktop 其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包;桌面端使用系统 WebKitGTK 4.0 和 DTK 5,终端 ARMv7 包不包含桌面端。 通过浏览器使用 安装终端版后,也可以启动后台服务。npm 安装时执行: 终端 · 启动 Web 服务 复制 acecode daemon 未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399 。便携版请在程序目录执行 ./acecode daemon 。配置了端口或认证时,按服务启动信息访问。 本机 Web 与远程访问 这里的地址用于本机访问。远程访问需要额外配置访问方式与认证;首次使用建议先在本机完成模型配置和任务验证。 请优先打开启动输出中的 Web UI: 地址;已保存的端口配置可能与默认值不同。完整操作见 Web 。" + "text": "安装前准备 先确定你使用的操作系统和处理器架构。Windows、macOS 和 Linux 的安装包不能混用;Apple 芯片与 Intel Mac 也需要选择不同的包。 模型访问方式: 准备服务商提供的 API Key,或使用支持的账号登录方式。安装完成后再配置即可。 开发环境: 若任务需要运行项目,请先准备项目依赖,例如 Git、Node.js、Python 或相应编译工具。 npm 安装: 需要电脑上已有 Node.js 和 npm。直接下载原生安装包时,不需要为了启动 ACECode 安装 Node.js。 所有手动下载入口都指向 ACECode 官方发布页 。在 Assets 中选择对应平台的产物,具体可用文件以该版本发布内容为准。 通过 npm 安装 如果你已经使用 npm,可以直接安装桌面端或终端版。安装时会按系统和处理器选择对应的原生程序。 桌面端 终端 · 安装并启动桌面端 复制 npm install -g @aceagent/desktop acecode-desktop 终端版 终端 · 安装、配置并启动 TUI 复制 npm install -g @aceagent/acecode acecode configure acecode 保留可选依赖 安装时请不要添加 --omit=optional 或 --no-optional ,平台所需的原生程序通过可选依赖安装。桌面端与 CLI 提供不同的启动命令,需要两个入口时可分别安装。 终端版与桌面端分别提供 acecode 与 acecode-desktop 命令。安装后重新打开终端,用 acecode --version 检查终端版是否可用。 Windows 桌面端可使用 Windows x64 安装器,也可以使用上方的 npm 方式。 打开官方发布页,找到该版本提供的 Windows 安装器,例如 ACECode-<版本>-windows-x64-setup.exe 。 双击安装器,按向导完成安装。默认安装到当前用户的 %LOCALAPPDATA%\\Programs\\ACECode 。 从开始菜单启动 ACECode,然后前往 配置第一个模型 。 Windows 安装向导的欢迎页和目标位置页。画面来自本机已有的 v0.9.6 安装包,仅展示向导流程,下载时请以发布页为准。本次查看后已退出向导,未执行安装。 Windows 实际界面 · 点击图片查看原图 Windows 桌面界面需要 Microsoft Edge WebView2 Runtime。若当前系统没有该运行时,请按安装提示补齐。使用便携包时,请保留解压后的完整目录,桌面程序需要同目录中的后台程序和资源文件。 macOS 先在苹果菜单的“关于本机”中确认芯片类型,再选择对应的安装包。 你的 Mac 选择的架构 Apple 芯片(M 系列) macos-arm64 Intel 处理器 macos-x64 若当前发布版本提供 .pkg ,下载与架构匹配的包并双击打开。 按照系统安装器提示完成当前用户安装。 在 ~/Applications/ACECode.app 打开 ACECode,继续配置模型。 GS-03 截图占位 macOS 安装与首次打开 此处将补充实际界面截图 16 : 9 macOS:展示 PKG 安装完成后,当前用户“应用程序”目录中的 ACECode。 若该版本没有 PKG,可使用 npm,或按发行说明使用对应架构的压缩包。ACECode 的 macOS 发布产物不使用 DMG 安装流程。 Linux Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz 。下面以 x64 压缩包为例,在下载目录执行: Shell · 从压缩包启动 复制 tar -xzf acecode-linux-x64.tar.gz cd acecode-linux-x64 ./acecode configure ./acecode ARM64 用户替换文件名和目录名中的 x64 为 arm64 。直接解压运行时使用 ./acecode ;将程序目录加入 PATH 后,才可在其他目录使用 acecode 。 使用 Linux 桌面端 桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动: Shell · 桌面运行库与启动 复制 sudo apt install libwebkit2gtk-4.1-0 ./acecode-desktop 其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包(x64、arm64);桌面端使用系统 WebKitGTK 4.0 和 DTK 5。1.0.7 是最后一个 Deepin ARMv7 版本。 通过浏览器使用 安装终端版后,也可以启动后台服务。npm 安装时执行: 终端 · 启动 Web 服务 复制 acecode daemon 未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399 。便携版请在程序目录执行 ./acecode daemon 。配置了端口或认证时,按服务启动信息访问。 本机 Web 与远程访问 这里的地址用于本机访问。远程访问需要额外配置访问方式与认证;首次使用建议先在本机完成模型配置和任务验证。 请优先打开启动输出中的 Web UI: 地址;已保存的端口配置可能与默认值不同。完整操作见 Web 。" }, { "page": "安装", @@ -62,7 +62,7 @@ window.ACECODE_HELP_INDEX = [ "title": "Linux", "url": "installation.html#linux", "description": "Linux Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz 。下面以 x64 压缩包为例,在下载目录执行: Shell · 从压缩包启动 复制", - "text": "Linux Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz 。下面以 x64 压缩包为例,在下载目录执行: Shell · 从压缩包启动 复制 tar -xzf acecode-linux-x64.tar.gz cd acecode-linux-x64 ./acecode configure ./acecode ARM64 用户替换文件名和目录名中的 x64 为 arm64 。直接解压运行时使用 ./acecode ;将程序目录加入 PATH 后,才可在其他目录使用 acecode 。 使用 Linux 桌面端 桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动: Shell · 桌面运行库与启动 复制 sudo apt install libwebkit2gtk-4.1-0 ./acecode-desktop 其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包;桌面端使用系统 WebKitGTK 4.0 和 DTK 5,终端 ARMv7 包不包含桌面端。 通过浏览器使用 安装终端版后,也可以启动后台服务。npm 安装时执行: 终端 · 启动 Web 服务 复制 acecode daemon 未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399 。便携版请在程序目录执行 ./acecode daemon 。配置了端口或认证时,按服务启动信息访问。 本机 Web 与远程访问 这里的地址用于本机访问。远程访问需要额外配置访问方式与认证;首次使用建议先在本机完成模型配置和任务验证。 请优先打开启动输出中的 Web UI: 地址;已保存的端口配置可能与默认值不同。完整操作见 Web 。" + "text": "Linux Linux 可以使用 npm 安装,也可以下载 acecode-linux-x64.tar.gz 或 acecode-linux-arm64.tar.gz 。下面以 x64 压缩包为例,在下载目录执行: Shell · 从压缩包启动 复制 tar -xzf acecode-linux-x64.tar.gz cd acecode-linux-x64 ./acecode configure ./acecode ARM64 用户替换文件名和目录名中的 x64 为 arm64 。直接解压运行时使用 ./acecode ;将程序目录加入 PATH 后,才可在其他目录使用 acecode 。 使用 Linux 桌面端 桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动: Shell · 桌面运行库与启动 复制 sudo apt install libwebkit2gtk-4.1-0 ./acecode-desktop 其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包(x64、arm64);桌面端使用系统 WebKitGTK 4.0 和 DTK 5。1.0.7 是最后一个 Deepin ARMv7 版本。 通过浏览器使用 安装终端版后,也可以启动后台服务。npm 安装时执行: 终端 · 启动 Web 服务 复制 acecode daemon 未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399 。便携版请在程序目录执行 ./acecode daemon 。配置了端口或认证时,按服务启动信息访问。 本机 Web 与远程访问 这里的地址用于本机访问。远程访问需要额外配置访问方式与认证;首次使用建议先在本机完成模型配置和任务验证。 请优先打开启动输出中的 Web UI: 地址;已保存的端口配置可能与默认值不同。完整操作见 Web 。" }, { "page": "快速开始", diff --git a/docs/help/installation.html b/docs/help/installation.html index ebd7d94d5..48c95a4f0 100644 --- a/docs/help/installation.html +++ b/docs/help/installation.html @@ -66,7 +66,7 @@

终端版

使用 Linux 桌面端

桌面端需要 WebKitGTK 4.1。采用 apt 的发行版可安装对应运行库,然后在程序目录中启动:

Shell · 桌面运行库与启动
sudo apt install libwebkit2gtk-4.1-0
 ./acecode-desktop
-

其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包;桌面端使用系统 WebKitGTK 4.0 和 DTK 5,终端 ARMv7 包不包含桌面端。

+

其他发行版请使用对应的软件包管理器安装 WebKitGTK 4.1。UOS / Deepin 用户请下载 acecode-linux-deepin-* 专用包(x64、arm64);桌面端使用系统 WebKitGTK 4.0 和 DTK 5。1.0.7 是最后一个 Deepin ARMv7 版本。

通过浏览器使用

安装终端版后,也可以启动后台服务。npm 安装时执行:

终端 · 启动 Web 服务
acecode daemon

未覆盖端口配置时,在本机浏览器打开 http://127.0.0.1:12399。便携版请在程序目录执行 ./acecode daemon。配置了端口或认证时,按服务启动信息访问。

diff --git a/docs/macos-release.md b/docs/macos-release.md index 86eb7ca7a..6d463402e 100644 --- a/docs/macos-release.md +++ b/docs/macos-release.md @@ -273,6 +273,15 @@ CI creates, notarizes, validates, and uploads the PKG before building the DMG, so a later DMG failure leaves the verified PKG available as a workflow artifact. Tagged releases still require the complete successful packaging matrix. +Every `stapler staple` / `stapler validate` in the packaging scripts goes through +`acecode_stapler` from [`macos_stapler.sh`](../scripts/macos_stapler.sh). Exit +code 68 means Apple's ticket service could not be reached (CloudKit dropped the +connection or asked the client to back off); it is retried up to six attempts, +15 seconds apart. Any other stapler failure still stops the job immediately. +Before this retry existed, v1.0.7's `macos-x64` job failed three runs in a row, +each time on one dropped connection. The contract test rejects new direct +`xcrun stapler` calls. + After preparing a trusted payload, an authorized release operator can run: ```bash diff --git a/docs/release-artifacts.md b/docs/release-artifacts.md index 17b885206..e83db6aba 100644 --- a/docs/release-artifacts.md +++ b/docs/release-artifacts.md @@ -1,6 +1,6 @@ # 正式发布产物约定 -`.github/workflows/package.yml` 的 `v*` 标签构建必须生成下列 18 个用户下载包。发布任务通过 `scripts/verify_release_assets.py` 检查每个文件只有一个非空副本,并拒绝不属于当前版本的包、未签名包或重名文件;检查失败时不创建 GitHub Release。 +`.github/workflows/package.yml` 的 `v*` 标签构建必须生成下列 17 个用户下载包。发布任务通过 `scripts/verify_release_assets.py` 检查每个文件只有一个非空副本,并拒绝不属于当前版本的包、未签名包或重名文件;检查失败时不创建 GitHub Release。 发布前先检查 `assets/models_dev/MANIFEST.json` 的快照时间不超过 30 天。刷新模型目录后必须执行 `pnpm --dir web sync:provider-logos`,同步官方图标与生成映射,再运行前端全量测试和构建;目录、图标 manifest 和映射的提供商数量必须一致。图标仍执行去重、单文件 100 KiB 和总量 576 KiB 上限,以及脚本/远程内容拒绝检查。 @@ -10,13 +10,13 @@ | --- | --- | --- | | Windows | x64、arm64 | `acecode-windows-.zip` | | Linux | x64、arm64、armv7 | `acecode-linux-.tar.gz` | -| UOS / Deepin | x64、arm64、armv7 | `acecode-linux-deepin-.tar.gz` | +| UOS / Deepin | x64、arm64 | `acecode-linux-deepin-.tar.gz` | | Linux 自动更新 | x64、arm64 | `acecode--linux--update.zip` | | macOS | x64、arm64 | `acecode-macos-.tar.gz` | | macOS 安装器 | x64、arm64 | `ACECode--macos-.dmg` 和 `.pkg` | | macOS 自动更新 | x64、arm64 | `ACECode--macos--update.zip` | -Deepin 构建是发布任务的必需依赖,不能因已有普通 Linux 包而跳过。它替代原 Linux old 流水线,保持 GLIBC 2.28 基线;x64/arm64 开启 `ACECODE_DEEPIN`,使用 WebKitGTK 4.0 和 DTK 5,ARMv7 保持 CLI-only。依赖及升级方式见 [UOS / Deepin 专用构建](deepin-desktop.md)。macOS 正式发布同时要求 Developer ID Application、Developer ID Installer 证书及公证凭据;安装器证书或密码缺失必须失败,不允许发布没有 PKG 的正式版。手动触发的开发构建仍可在缺少签名凭据时生成非正式产物,不创建 Release。 +Deepin 构建是发布任务的必需依赖,不能因已有普通 Linux 包而跳过。它替代原 Linux old 流水线,保持 GLIBC 2.28 基线;x64/arm64 开启 `ACECODE_DEEPIN`,使用 WebKitGTK 4.0 和 DTK 5。Deepin ARMv7 自 1.0.7 之后不再发布;发布检查会把混进来的 `acecode-linux-deepin-armv7.tar.gz` 当作意外资产拒绝。依赖及升级方式见 [UOS / Deepin 专用构建](deepin-desktop.md)。macOS 正式发布同时要求 Developer ID Application、Developer ID Installer 证书及公证凭据;安装器证书或密码缺失必须失败,不允许发布没有 PKG 的正式版。手动触发的开发构建仍可在缺少签名凭据时生成非正式产物,不创建 Release。macOS 打包脚本里的 stapler 调用一律经 `scripts/macos_stapler.sh` 的 `acecode_stapler`:退出码 68(连不上 Apple 票据服务)时原样重试,最多尝试 6 次、间隔 15 秒,其它错误照常立即失败。 调试符号使用 `dev_only.` 文件名前缀;避免方括号等会被 GitHub 改写的字符,使 `SHA256SUMS.txt` 中的文件名与下载名一致。 diff --git a/openspec/changes/retire-deepin-armv7-and-retry-stapler/.openspec.yaml b/openspec/changes/retire-deepin-armv7-and-retry-stapler/.openspec.yaml new file mode 100644 index 000000000..0cb476561 --- /dev/null +++ b/openspec/changes/retire-deepin-armv7-and-retry-stapler/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-16 diff --git a/openspec/changes/retire-deepin-armv7-and-retry-stapler/proposal.md b/openspec/changes/retire-deepin-armv7-and-retry-stapler/proposal.md new file mode 100644 index 000000000..3a0b0f34b --- /dev/null +++ b/openspec/changes/retire-deepin-armv7-and-retry-stapler/proposal.md @@ -0,0 +1,31 @@ +## 为什么 + +v1.0.7 的 `v*` 标签构建有两个平台失败,最后都靠基于同一 tag 源码的恢复工作流补出来: + +- `linux-deepin-armv7`:buster 容器里 `archive.debian.org` 的 CDN 对确实存在的 `.deb` 长期返回缓存 404,交叉编译依赖装不上。用户决定以后不再发布 Deepin ARMv7 包,不为它补下载容错。 +- `macos-x64`:签名后 `xcrun stapler` 反复连不上 Apple 票据服务(CloudKit `-1005`,退出码 68),重跑三次每次断在不同的 stapler 调用上。v1.0.6 也遇到过同样的错误。一个 job 要调十几次 stapler,任何一次断线都会让整个发布失败。 + +## 变更内容 + +- 从 `package.yml` 的 Deepin 构建矩阵移除 `linux-deepin-armv7`,连同它专用的交叉编译依赖、ARMv7 可执行文件校验和交叉 binutils 前缀。 +- 正式发布的必需产物从 18 个减为 17 个;`acecode-linux-deepin-armv7.tar.gz` 出现时按意外资产拒绝发布。普通 `acecode-linux-armv7.tar.gz` 不变。 +- 新增 `scripts/macos_stapler.sh` 的 `acecode_stapler`:只对退出码 68 重试,默认最多 6 次、间隔 15 秒;其它退出码立即返回。macOS 打包脚本与安装器构建脚本里的 stapler 调用全部改走它。 +- 契约测试覆盖重试成功、持续 68 后放弃、非 68 不重试,并拒绝新增直接 `xcrun stapler` 调用。 + +## 能力 + +### 新增能力 + +- `macos-stapler-retry`:macOS 打包流程对 Apple 票据服务的瞬时断线重试,其它 stapler 失败照常终止。 + +### 修改能力 + +- `deepin-release-packaging`:Deepin 专用发行族只保留 x64 / arm64 两个归档。 + +## 影响 + +- `.github/workflows/package.yml` 的 Deepin 构建 job。 +- `scripts/verify_release_assets.py` 与 `tests/scripts/verify_release_assets_test.py`。 +- `scripts/macos_notarize_app.sh`、`scripts/macos_notarize_pkg.sh`、`scripts/macos_create_installer_dmg.sh`、`scripts/macos_create_update_zip.sh`、`packaging/macos-installer/build.sh`,以及 `tests/scripts/macos_installer_release_test.py`、`tests/scripts/macos_release_scripts_test.sh`。 +- `docs/deepin-desktop.md`、`docs/release-artifacts.md`、`docs/macos-release.md`,帮助站点安装页(`docs/help-source/getting-started.json` 及生成的 `docs/help`)。 +- 已安装 Deepin ARMv7 包的用户不再收到新版本;升级代码里的 `linux-deepin-armv7` 更新标识保持不变(Deepin 包本来就不进自动更新清单)。 diff --git a/openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/deepin-release-packaging/spec.md b/openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/deepin-release-packaging/spec.md new file mode 100644 index 000000000..9b4e8f4a8 --- /dev/null +++ b/openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/deepin-release-packaging/spec.md @@ -0,0 +1,16 @@ +## MODIFIED Requirements + +### Requirement: Dedicated release family +Release packaging SHALL replace `linux-old` archives with `acecode-linux-deepin-.tar.gz` for x64 and arm64, both with Desktop coverage, and explicitly select the dedicated build variant. Deepin ARMv7 SHALL NOT be built or published after 1.0.7. Desktop packages MUST use WebKitGTK 4.0 and DTK 5; the maximum required GLIBC version SHALL remain 2.28. + +#### Scenario: Complete release +- **WHEN** a tagged release is assembled +- **THEN** the x64 and arm64 Deepin archives are required along with the existing generic platform artifacts + +#### Scenario: Retired ARMv7 archive appears +- **WHEN** the release artifacts contain `acecode-linux-deepin-armv7.tar.gz` +- **THEN** release verification rejects it as an unexpected asset and no Release is created + +#### Scenario: Incompatible Desktop linkage +- **WHEN** the dedicated Desktop links WebKitGTK 4.1/6, lacks DTK 5 or requires GLIBC above 2.28 +- **THEN** the packaging verification fails diff --git a/openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/macos-stapler-retry/spec.md b/openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/macos-stapler-retry/spec.md new file mode 100644 index 000000000..7498ec3da --- /dev/null +++ b/openspec/changes/retire-deepin-armv7-and-retry-stapler/specs/macos-stapler-retry/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: Apple 票据服务断线必须重试 +macOS 打包流程中的每一次 `stapler staple` / `stapler validate` SHALL 经 `scripts/macos_stapler.sh` 的 `acecode_stapler` 执行。退出码 68(无法连接 Apple 票据服务)MUST 原样重试同一命令,默认最多 6 次、每次间隔 15 秒;其它退出码 MUST 立即返回,不得重试。 + +#### Scenario: 断线后恢复 +- **WHEN** stapler 前两次因票据服务断线返回 68,第三次成功 +- **THEN** 同一命令被重试,打包继续并产出可信资产 + +#### Scenario: 票据服务持续不可用 +- **WHEN** stapler 在全部重试次数内都返回 68 +- **THEN** 该步骤以 68 失败,不继续公证或产出后续安装器 + +#### Scenario: 非断线类 stapler 错误 +- **WHEN** stapler 因票据缺失或签名无效返回 68 以外的退出码 +- **THEN** 不重试,步骤立即失败 + +### Requirement: 不得绕过重试直接调用 stapler +`scripts/macos_*.sh` 与 `packaging/macos-installer/build.sh` 中除 `scripts/macos_stapler.sh` 外 MUST NOT 直接调用 `xcrun stapler`;契约测试 SHALL 在出现直接调用时失败。 + +#### Scenario: 新增直接调用 +- **WHEN** 有脚本新增一条 `xcrun stapler` 调用 +- **THEN** `tests/scripts/macos_release_scripts_test.sh` 失败并提示改用 `acecode_stapler` diff --git a/openspec/changes/retire-deepin-armv7-and-retry-stapler/tasks.md b/openspec/changes/retire-deepin-armv7-and-retry-stapler/tasks.md new file mode 100644 index 000000000..8b96d714d --- /dev/null +++ b/openspec/changes/retire-deepin-armv7-and-retry-stapler/tasks.md @@ -0,0 +1,18 @@ +## 1. 停止发布 Deepin ARMv7 + +- [x] 1.1 从 `package.yml` 的 Deepin 矩阵移除 `linux-deepin-armv7` 及其交叉编译依赖、ARMv7 校验步骤和交叉 binutils 前缀 +- [x] 1.2 必需产物改为 17 个,混入的 Deepin ARMv7 归档按意外资产拒绝,并补测试 +- [x] 1.3 更新 `docs/deepin-desktop.md`、`docs/release-artifacts.md` 与帮助站点安装页(`docs/help-source/getting-started.json` 重新生成) + +## 2. stapler 断线重试 + +- [x] 2.1 新增 `scripts/macos_stapler.sh`,只对退出码 68 重试 +- [x] 2.2 公证、安装器 DMG、更新 ZIP、安装器构建脚本的 stapler 调用全部改走 `acecode_stapler` +- [x] 2.3 契约测试覆盖重试成功、持续失败、非 68 不重试,并拒绝直接 `xcrun stapler` +- [x] 2.4 更新 `docs/macos-release.md`、`docs/release-artifacts.md` + +## 3. 验证 + +- [x] 3.1 本机运行 `verify_release_assets_test.py`、`macos_release_scripts_test.sh`(含变异检查)、Shell 语法检查与 `git diff --check` +- [ ] 3.2 PR 的 `macos-installer-tests`(macOS 上的 `macos_installer_release_test.py`)通过 +- [ ] 3.3 下一次 `v*` 标签构建确认 Deepin 只产出 x64 / arm64,macOS 两个架构完整 diff --git a/packaging/macos-installer/build.sh b/packaging/macos-installer/build.sh index ce915a1b4..f0759fa6d 100644 --- a/packaging/macos-installer/build.sh +++ b/packaging/macos-installer/build.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash # Production adaptation of the v6 local installer build; never signs the payload. set -euo pipefail +# shellcheck source=scripts/macos_stapler.sh +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd -P)/scripts/macos_stapler.sh" usage() { printf 'Usage: bash packaging/macos-installer/build.sh --app ACECode.app --output NEW-DIR --arch x64|arm64\n' } @@ -34,7 +36,7 @@ done details="$(/usr/bin/codesign -dv --verbose=4 "$source_app" 2>&1)" grep -Fxq 'TeamIdentifier=T52GZCH73Y' <<< "$details" grep -Fq 'Authority=Developer ID Application:' <<< "$details" -xcrun stapler validate "$source_app" +acecode_stapler validate "$source_app" /usr/sbin/spctl --assess --type execute "$source_app" mkdir -p "$output" output="$(cd "$output" && pwd -P)" diff --git a/scripts/macos_create_installer_dmg.sh b/scripts/macos_create_installer_dmg.sh index e71243235..f1047e6ea 100644 --- a/scripts/macos_create_installer_dmg.sh +++ b/scripts/macos_create_installer_dmg.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail set +x +# shellcheck source=scripts/macos_stapler.sh +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/macos_stapler.sh" usage() { printf 'Usage: bash scripts/macos_create_installer_dmg.sh --app ACECode.app --output NEW.dmg --arch x64|arm64 --identity APPLICATION-IDENTITY [--keychain PATH] [--keychain-profile NAME]\n' } @@ -55,15 +57,15 @@ notarize() { if [[ -n "$submission" ]]; then xcrun notarytool log "$submission" "${auth[@]}" || true; fi printf 'Notarization rejected: %s\n' "$status" >&2; exit 1 fi - xcrun stapler staple "$ticket" - xcrun stapler validate "$ticket" + acecode_stapler staple "$ticket" + acecode_stapler validate "$ticket" } /usr/bin/ditto -c -k --keepParent "$installer" "$work/installer.zip" notarize "$work/installer.zip" "$installer" /usr/bin/codesign --verify --deep --strict "$installer" /usr/sbin/spctl --assess --type execute --verbose=4 "$installer" /usr/bin/diff -rq "$app" "$installer/Contents/Resources/ACECode.app" -xcrun stapler validate "$installer/Contents/Resources/ACECode.app" +acecode_stapler validate "$installer/Contents/Resources/ACECode.app" # The image root contains only the installer, not a misleading Applications link. # DiskImages can transiently fail with EBUSY on hosted macOS runners. Keep each # attempt separate, capture logs without a pipe, and retry only that error. diff --git a/scripts/macos_create_update_zip.sh b/scripts/macos_create_update_zip.sh index 36466d9a0..d579ff10d 100755 --- a/scripts/macos_create_update_zip.sh +++ b/scripts/macos_create_update_zip.sh @@ -22,6 +22,8 @@ output_path="" require_trusted=false script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" repo_root="$(cd -- "$script_dir/.." && pwd -P)" +# shellcheck source=scripts/macos_stapler.sh +source "$script_dir/macos_stapler.sh" while [[ $# -gt 0 ]]; do case "$1" in @@ -167,7 +169,7 @@ if [[ "$require_trusted" == true ]]; then /usr/bin/codesign --verify --deep --strict --verbose=2 "$verified_app" /usr/bin/codesign --verify --strict --verbose=2 "$verified_cli" /usr/bin/codesign --verify --strict --verbose=2 "$verified_app/Contents/MacOS/acecode-computer-use" - xcrun stapler validate "$verified_app" + acecode_stapler validate "$verified_app" /usr/sbin/spctl --assess --type execute --verbose=4 "$verified_app" fi diff --git a/scripts/macos_notarize_app.sh b/scripts/macos_notarize_app.sh index afe937807..43c332a7d 100755 --- a/scripts/macos_notarize_app.sh +++ b/scripts/macos_notarize_app.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail set +x +# shellcheck source=scripts/macos_stapler.sh +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/macos_stapler.sh" usage() { cat <<'USAGE' @@ -115,8 +117,8 @@ if [[ "$status" != "Accepted" ]]; then fi echo "Stapling notarization ticket to $app_path" -xcrun stapler staple "$app_path" -xcrun stapler validate "$app_path" +acecode_stapler staple "$app_path" +acecode_stapler validate "$app_path" /usr/bin/codesign --verify --deep --strict --verbose=2 "$app_path" echo "Assessing $app_path with Gatekeeper" diff --git a/scripts/macos_notarize_pkg.sh b/scripts/macos_notarize_pkg.sh index ba3a1440e..59082a006 100755 --- a/scripts/macos_notarize_pkg.sh +++ b/scripts/macos_notarize_pkg.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -euo pipefail set +x +# shellcheck source=scripts/macos_stapler.sh +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)/macos_stapler.sh" usage() { cat <<'USAGE' @@ -123,8 +125,8 @@ if [[ "$notary_status" != "Accepted" ]]; then fi echo "Stapling notarization ticket to $pkg_path" -xcrun stapler staple "$pkg_path" -xcrun stapler validate "$pkg_path" +acecode_stapler staple "$pkg_path" +acecode_stapler validate "$pkg_path" post_staple_signature="$(/usr/sbin/pkgutil --check-signature "$pkg_path" 2>&1)" || { printf '%s\n' "$post_staple_signature" >&2 exit 1 diff --git a/scripts/macos_stapler.sh b/scripts/macos_stapler.sh new file mode 100644 index 000000000..d4db8cc3a --- /dev/null +++ b/scripts/macos_stapler.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Sourced by the macOS packaging scripts; it only defines acecode_stapler. +# +# `xcrun stapler` exits 68 when it cannot reach Apple's ticket service: CloudKit +# drops the connection (NSURLErrorDomain -1005 against api.apple-cloudkit.com) or +# the service asks the client to back off. The notarization has already been +# accepted at that point, so the failure is transient. A release job calls +# stapler about a dozen times; v1.0.7's macos-x64 job failed three runs in a row, +# each time on a different call, and v1.0.6 hit the same error. Retry only 68; +# a missing ticket, an invalid signature or any other failure still stops the +# build at once. Stapling and validating are idempotent, so a retry is safe. +# +# ACECODE_STAPLER_ATTEMPTS (default 6) and ACECODE_STAPLER_RETRY_DELAY (seconds, +# default 15) exist for the offline contract tests. + +acecode_stapler() { + local attempts="${ACECODE_STAPLER_ATTEMPTS:-6}" + local delay="${ACECODE_STAPLER_RETRY_DELAY:-15}" + local attempt=1 + local result=0 + while true; do + if xcrun stapler "$@"; then + return 0 + else + result=$? + fi + if [[ "$result" -ne 68 || "$attempt" -ge "$attempts" ]]; then + return "$result" + fi + echo "xcrun stapler $1: Apple ticket service unavailable (68), retry $attempt/$((attempts - 1)) in ${delay}s" >&2 + sleep "$delay" + attempt=$((attempt + 1)) + done +} diff --git a/scripts/verify_release_assets.py b/scripts/verify_release_assets.py index ed164f2be..a3d86bf3e 100644 --- a/scripts/verify_release_assets.py +++ b/scripts/verify_release_assets.py @@ -11,8 +11,9 @@ def required_asset_names(version): if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?", version): raise ValueError("Invalid release version") names = [f"acecode-windows-{arch}.zip" for arch in ("x64", "arm64")] - names += [f"acecode-{platform}-{arch}.tar.gz" - for platform in ("linux", "linux-deepin") for arch in ("x64", "arm64", "armv7")] + names += [f"acecode-linux-{arch}.tar.gz" for arch in ("x64", "arm64", "armv7")] + # Deepin ARMv7 stopped shipping after 1.0.7; a stray archive is rejected as unexpected. + names += [f"acecode-linux-deepin-{arch}.tar.gz" for arch in ("x64", "arm64")] for arch in ("x64", "arm64"): names += [f"acecode-macos-{arch}.tar.gz", f"acecode-{version}-linux-{arch}-update.zip", diff --git a/tests/scripts/macos_installer_release_test.py b/tests/scripts/macos_installer_release_test.py index 1a6947e7f..b7cafdd81 100644 --- a/tests/scripts/macos_installer_release_test.py +++ b/tests/scripts/macos_installer_release_test.py @@ -59,6 +59,16 @@ def value(flag): return args[args.index(flag) + 1] status = os.environ.get('NOTARY_STATUS', 'Accepted') if args[2].endswith('.dmg') and os.environ.get('REJECT_DMG'): status = 'Invalid' print(json.dumps({'status': status, 'id': 'test-submission'})) + elif args[0] == 'stapler': + counter = root / 'stapler-attempts' + attempt = int(counter.read_text()) + 1 if counter.exists() else 1 + counter.write_text(str(attempt)) + if os.environ.get('STAPLER_FATAL'): + print('The staple and validate action failed! Error 65.', file=sys.stderr) + sys.exit(65) + if attempt <= int(os.environ.get('STAPLER_TICKET_FAILURES', '0')): + print('CloudKit: The network connection was lost. The validate action failed! Error 68.', file=sys.stderr) + sys.exit(68) elif tool == 'lipo': assert args[1] == '-verify_arch' and args[2] in ('x86_64', 'arm64') if os.environ.get('WRONG_ARCH'): sys.exit(1) @@ -77,7 +87,8 @@ def setUp(self): path = self.tools / tool path.write_text('#!/bin/bash\nexec python3 "$(dirname "$0")/mock.py" ' + tool + ' "$@"\n') path.chmod(0o755) - for name in ('scripts/macos_create_installer_dmg.sh', 'packaging/macos-installer/build.sh'): + for name in ('scripts/macos_create_installer_dmg.sh', 'scripts/macos_stapler.sh', + 'packaging/macos-installer/build.sh'): target = self.root / name target.parent.mkdir(parents=True, exist_ok=True) text = (ROOT / name).read_text() @@ -91,7 +102,8 @@ def setUp(self): for name in ('MacOS/ACECode', 'MacOS/acecode-daemon', 'Resources/app.icns', 'Info.plist'): (self.app / 'Contents' / name).write_text('original') self.output = self.root / 'output/ACECode-1.2.3-macos-x64.dmg' - self.env = dict(os.environ, TEST_ROOT=str(self.root), PATH=str(self.tools) + os.pathsep + os.environ['PATH']) + self.env = dict(os.environ, TEST_ROOT=str(self.root), PATH=str(self.tools) + os.pathsep + os.environ['PATH'], + ACECODE_STAPLER_RETRY_DELAY='0') def run_pipeline(self, success=True, arch='x64'): result = subprocess.run(['bash', str(self.root / 'scripts/macos_create_installer_dmg.sh'), @@ -159,6 +171,38 @@ def test_image_errors_other_than_busy_are_not_retried(self): self.assertEqual(sum(c[0] == 'hdiutil' for c in calls), 1) self.assertFalse(self.output.exists()) + def test_ticket_service_disconnects_are_retried(self): + # 场景:staple/validate 时连 Apple 票据服务(CloudKit)断线,stapler 退出码 68。 + # v1.0.7 的 macos-x64 正是这样整个 job 失败,重跑三次每次断在不同步骤(v1.0.6 也遇到过)。 + # 期望:同一条 stapler 命令原样重试,恢复后流水线照常产出 DMG。 + self.env['STAPLER_TICKET_FAILURES'] = '2' + calls = self.run_pipeline() + staples = [c for c in calls if c[:2] == ['xcrun', 'stapler']] + self.assertGreaterEqual(len(staples), 3) + self.assertEqual(staples[0], staples[1]) + self.assertEqual(staples[1], staples[2]) + self.assertEqual(self.output.read_text(), 'dmg') + + def test_persistent_ticket_service_errors_stop_without_publishing(self): + # 场景:票据服务一直不可用。期望:重试满 ACECODE_STAPLER_ATTEMPTS 次后失败, + # 不再继续公证或产出 DMG(第一处 stapler 在构建安装器时校验原始应用)。 + self.env['STAPLER_TICKET_FAILURES'] = '99' + self.env['ACECODE_STAPLER_ATTEMPTS'] = '3' + calls = self.run_pipeline(success=False) + staples = [c for c in calls if c[:2] == ['xcrun', 'stapler']] + self.assertEqual(len(staples), 3) + self.assertEqual(len({json.dumps(c) for c in staples}), 1) + self.assertFalse(any(c[:3] == ['xcrun', 'notarytool', 'submit'] for c in calls)) + self.assertFalse(self.output.exists()) + + def test_stapler_errors_other_than_ticket_service_are_not_retried(self): + # 场景:stapler 因票据缺失、签名无效等原因失败(非 68)。 + # 期望:不重试,立即失败,避免把真正的签名问题拖成长时间等待。 + self.env['STAPLER_FATAL'] = '1' + calls = self.run_pipeline(success=False) + self.assertEqual(sum(c[:2] == ['xcrun', 'stapler'] for c in calls), 1) + self.assertFalse(self.output.exists()) + def test_cli_validation_without_tools(self): for script in ('packaging/macos-installer/build.sh', 'scripts/macos_create_installer_dmg.sh'): for args, status in [(['--help'], 0), ([], 2), (['--arch'], 2), (['--unknown'], 2)]: diff --git a/tests/scripts/macos_release_scripts_test.sh b/tests/scripts/macos_release_scripts_test.sh index 8658de4c4..6bb04a5c6 100755 --- a/tests/scripts/macos_release_scripts_test.sh +++ b/tests/scripts/macos_release_scripts_test.sh @@ -40,6 +40,48 @@ for script in "$sign_script" "$pkg_script" "$notarize_pkg_script" \ bash "$script" --definitely-unknown done +stapler_helper="$repo_root/scripts/macos_stapler.sh" +bash -n "$stapler_helper" + +# 场景:stapler 连不上 Apple 票据服务时退出码为 68(v1.0.6 / v1.0.7 的 macos-x64 因此整个 +# job 失败)。期望:acecode_stapler 只对 68 重试,恢复即成功;一直 68 时在上限处放弃并返回 68; +# 其它退出码一次就返回。用 shell 函数假冒 xcrun,不依赖 macOS。 +( + # shellcheck source=../../scripts/macos_stapler.sh + source "$stapler_helper" + stapler_calls=0 + stapler_failures=0 + stapler_code=68 + xcrun() { + stapler_calls=$((stapler_calls + 1)) + [[ "$1 $2 $3" == "stapler validate fixture.app" ]] || return 99 + if [[ "$stapler_calls" -le "$stapler_failures" ]]; then return "$stapler_code"; fi + return 0 + } + export ACECODE_STAPLER_RETRY_DELAY=0 + + stapler_calls=0; stapler_failures=2 + acecode_stapler validate fixture.app 2>/dev/null + [[ "$stapler_calls" -eq 3 ]] || { echo "transient 68 should be retried: $stapler_calls calls" >&2; exit 1; } + + stapler_calls=0; stapler_failures=99; result=0 + ACECODE_STAPLER_ATTEMPTS=4 acecode_stapler validate fixture.app 2>/dev/null || result=$? + [[ "$result" -eq 68 && "$stapler_calls" -eq 4 ]] || { + echo "persistent 68 should stop after 4 attempts: exit $result, $stapler_calls calls" >&2; exit 1; } + + stapler_calls=0; stapler_failures=99; stapler_code=65; result=0 + acecode_stapler validate fixture.app 2>/dev/null || result=$? + [[ "$result" -eq 65 && "$stapler_calls" -eq 1 ]] || { + echo "non-ticket stapler errors must not be retried: exit $result, $stapler_calls calls" >&2; exit 1; } +) + +# 守卫:新增的 stapler 调用必须走 acecode_stapler,否则一次断线又会让整个发布 job 失败。 +if grep -n 'xcrun stapler' "$repo_root"/scripts/macos_*.sh "$repo_root/packaging/macos-installer/build.sh" | + grep -v '/scripts/macos_stapler.sh:'; then + echo "Call acecode_stapler from scripts/macos_stapler.sh instead of xcrun stapler" >&2 + exit 1 +fi + grep -Fq -- '--keychain-profile ' "$notarize_pkg_script" grep -Fq -- '--keychain-profile ' "$notarize_app_script" grep -Fq -- '--installer-identity ' "$pkg_script" diff --git a/tests/scripts/verify_release_assets_test.py b/tests/scripts/verify_release_assets_test.py index 7381b4d30..694375446 100644 --- a/tests/scripts/verify_release_assets_test.py +++ b/tests/scripts/verify_release_assets_test.py @@ -20,14 +20,24 @@ def setUp(self): path.parent.mkdir() path.write_bytes(b"package") - def test_complete_release_requires_all_eighteen_downloads(self): + def test_complete_release_requires_all_seventeen_downloads(self): selected = assets.verify_release_assets(self.root, "1.2.3") - self.assertEqual(len(selected), 18) - for arch in ("x64", "arm64", "armv7"): + self.assertEqual(len(selected), 17) + for arch in ("x64", "arm64"): self.assertIn(f"acecode-linux-deepin-{arch}.tar.gz", self.names) + self.assertIn("acecode-linux-armv7.tar.gz", self.names) for arch in ("x64", "arm64"): self.assertIn(f"ACECode-1.2.3-macos-{arch}.pkg", self.names) + def test_retired_deepin_armv7_archive_is_rejected(self): + # 场景:1.0.7 之后不再发布 Deepin ARMv7(用户决定),但产物目录里混进了一个 + # acecode-linux-deepin-armv7.tar.gz(旧 run、恢复构建残留)。 + # 期望:它不在必需清单里,出现时按「意外资产」拒绝发布,而不是被悄悄传上 Release。 + self.assertNotIn("acecode-linux-deepin-armv7.tar.gz", self.names) + (self.root / "acecode-linux-deepin-armv7.tar.gz").write_bytes(b"package") + with self.assertRaisesRegex(ValueError, "Unexpected release asset: acecode-linux-deepin-armv7.tar.gz"): + assets.verify_release_assets(self.root, "1.2.3") + def test_every_missing_or_empty_asset_blocks_publication(self): for name in self.names: with self.subTest(name=name): @@ -41,7 +51,7 @@ def test_every_missing_or_empty_asset_blocks_publication(self): path.write_bytes(b"package") def test_old_linux_archives_cannot_replace_deepin_packages(self): - for arch in ("x64", "arm64", "armv7"): + for arch in ("x64", "arm64"): path = next(self.root.rglob(f"acecode-linux-deepin-{arch}.tar.gz")) path.rename(path.with_name(f"acecode-linux-old-{arch}.tar.gz")) with self.assertRaises(ValueError) as error: @@ -75,7 +85,7 @@ def test_version_mismatch_or_invalid_version_is_rejected(self): def test_non_package_artifacts_do_not_affect_runtime_completeness(self): for name in ("acecode.debug", "acecode.pdb", "index.html"): (self.root / name).write_bytes(b"auxiliary") - self.assertEqual(len(assets.verify_release_assets(self.root, "1.2.3")), 18) + self.assertEqual(len(assets.verify_release_assets(self.root, "1.2.3")), 17) if __name__ == "__main__": From 27c60405961655ea6bb2303e60ff5280b8a732be Mon Sep 17 00:00:00 2001 From: haozhi shao Date: Fri, 16 Oct 2026 09:17:35 +0800 Subject: [PATCH 2/2] Copy the stapler helper into the update-zip test fixture macos_create_update_zip.sh now sources scripts/macos_stapler.sh from its own directory; the dedup test copies the script into a temporary tree and must copy the helper beside it. Co-Authored-By: Claude Opus 5.5 --- tests/scripts/macos_update_dedup_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/scripts/macos_update_dedup_test.py b/tests/scripts/macos_update_dedup_test.py index aace06a77..d088716d3 100644 --- a/tests/scripts/macos_update_dedup_test.py +++ b/tests/scripts/macos_update_dedup_test.py @@ -75,6 +75,8 @@ def setUp(self): scripts.mkdir() shutil.copytree(ROOT / 'assets/seed', self.root / 'assets/seed') shutil.copy2(ROOT / 'scripts/verify_seed_bundle.py', scripts / 'verify_seed_bundle.py') + # The update script sources its stapler retry helper from its own directory. + shutil.copy2(ROOT / 'scripts/macos_stapler.sh', scripts / 'macos_stapler.sh') text = (ROOT / 'scripts/macos_create_update_zip.sh').read_text() text = text.replace('/usr/bin/ditto', '"' + str(ditto) + '"') self.script = scripts / 'macos_create_update_zip.sh'