Merge pull request #10271 from SivanCola/fix/v1.38.8-manual-desktop-release

fix(release): recover v1.38.8 manual downloads / 恢复桌面版手动下载发布
This commit is contained in:
SivanCola authored and GitHub committed 2026-09-14 15:05:34 +08:00
commit 09cdab3866
5 files changed
+103 -7

No files matched your search

+50 -6
View File
@@ -94,6 +94,11 @@ on:
required: false
default: ""
type: string
desktop_manual_only:
description: "One-time approved v1.38.8 manual Desktop recovery"
required: false
default: false
type: boolean
concurrency:
# A channel pointer is a monotonic public state machine. Serialize every
@@ -225,6 +230,12 @@ jobs:
env:
PREFLIGHT_ARTIFACT_PREFIX: ${{ inputs.preflight_artifact_prefix }}
run: |
if [ "${{ inputs.desktop_manual_only }}" = "true" ]; then
if [ "${{ inputs.orchestrated }}" != "true" ] || [ "${{ inputs.channel }}" != "stable" ] || [ "${{ needs.resolve.outputs.tag }}" != "desktop-v1.38.8" ] || [ "${{ needs.resolve.outputs.sha }}" != "7278072720a2dc7a31cce0eec18c1eacc149c0e0" ]; then
echo "::error::manual Desktop exception is restricted to the approved immutable v1.38.8 candidate"
exit 1
fi
fi
if [ "${{ inputs.production_signing_smoke }}" = "true" ] && [ "${{ inputs.signing_preflight }}" = "true" ]; then
echo "::error::production_signing_smoke and signing_preflight are mutually exclusive"
exit 1
@@ -316,7 +327,7 @@ jobs:
env:
# Windows Authenticode signing engages only when the SignPath token is set, so
# forks / token-less runs still build (unsigned), mirroring the APPLE_* gate.
HAS_SIGNPATH: ${{ secrets.SIGNPATH_API_TOKEN != '' }}
HAS_SIGNPATH: ${{ secrets.SIGNPATH_API_TOKEN != '' && !inputs.desktop_manual_only }}
SIGNPATH_SOURCE_SHA: ${{ needs.resolve.outputs.sha }}
SIGNPATH_CONTROL_SHA: ${{ github.workflow_sha }}
SIGNPATH_FINGERPRINT: ${{ needs.signing-contract.outputs.fingerprint }}
@@ -399,11 +410,10 @@ jobs:
echo "$APPLE_API_KEY_P8" | base64 --decode > "$RUNNER_TEMP/notary.p8"
rm -f "$RUNNER_TEMP/cert.p12"
# Official Windows releases must fail closed. Forks can still exercise
# unsigned packaging without repository secrets, but esengine releases
# may never publish an unsigned payload or installer.
# Official automatic-update releases require Authenticode. The guarded
# v1.38.8 manual-only recovery retains minisign without advancing updates.
- name: Require Windows Authenticode signing
if: runner.os == 'Windows' && github.repository == 'esengine/DeepSeek-Reasonix'
if: runner.os == 'Windows' && github.repository == 'esengine/DeepSeek-Reasonix' && !inputs.desktop_manual_only
run: |
if [ "$HAS_SIGNPATH" != "true" ]; then
echo "::error::SIGNPATH_API_TOKEN is required for public Windows Preview and Stable releases"
@@ -733,6 +743,20 @@ jobs:
if-no-files-found: error
retention-days: 30
- name: Bind manual Windows payload and rebuild packages
if: runner.os == 'Windows' && inputs.desktop_manual_only
env:
MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }}
MINISIGN_PASSWORD: ${{ secrets.MINISIGN_PASSWORD }}
REASONIX_REQUIRE_PAYLOAD_MANIFEST: "1"
run: |
cd desktop
go run ./cmd/sign windows-payload build/windows/signing-payload "${{ needs.resolve.outputs.version }}"
go run ./cmd/sign sign build/windows/signing-payload/reasonix-payload.json
go run ./cmd/sign verify build/windows/signing-payload/reasonix-payload.json
cd ..
scripts/package-windows-desktop.sh "${{ matrix.platform == 'windows/arm64' && 'arm64' || 'amd64' }}" desktop/build/windows/signing-payload
- name: Sign artifacts (minisign)
working-directory: desktop
env:
@@ -915,10 +939,23 @@ jobs:
APPROVED_SHA: ${{ inputs.approved_sha }}
run: bash scripts/verify-release-tag.sh
- name: Disclose manual Desktop distribution
if: ${{ inputs.desktop_manual_only }}
run: |
cat >> /tmp/release-notes.md <<'NOTES'
## Manual desktop downloads / 桌面版手动下载
This v1.38.8 desktop release is manual-download only on every platform. Windows packages do not carry Reasonix Authenticode signatures because SignPath signing is unavailable; Windows may show an unknown-publisher warning. Detached minisign signatures and SHA-256 checksums remain available. Desktop automatic-update channels remain on v1.38.7. CLI and npm distribution are unaffected.
本次 v1.38.8 桌面版所有平台均需手动下载安装。因 SignPath 签名服务暂不可用,Windows 包不含 Reasonix Authenticode 签名,系统可能显示未知发布者提示;仍提供 minisign 签名和 SHA-256 校验。桌面自动更新通道保留在 v1.38.7,CLI 和 npm 正常发布。
NOTES
- name: Publish GitHub release
if: needs.resolve.outputs.channel != 'preview'
env:
GH_TOKEN: ${{ github.token }}
DESKTOP_MANUAL_ONLY: ${{ inputs.desktop_manual_only || false }}
# Keep the repository homepage focused on the installable desktop app;
# the CLI release line is configured not to claim repository-wide latest.
run: >-
@@ -1093,6 +1130,7 @@ jobs:
id: mirror_r2
if: env.HAS_R2 == 'true'
env:
DESKTOP_MANUAL_ONLY: ${{ inputs.desktop_manual_only || false }}
CHANNEL: ${{ needs.resolve.outputs.channel }}
PRERELEASE: ${{ needs.resolve.outputs.prerelease }}
R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }}
@@ -1228,6 +1266,12 @@ jobs:
bash release-control/scripts/compare-desktop-release-manifests.sh \
assets/latest.json /tmp/reasonix-desktop-tag-latest.json
if [ "$DESKTOP_MANUAL_ONLY" = "true" ]; then
echo "pointer_moved=false" >> "$GITHUB_OUTPUT"
echo "Manual Desktop release: immutable downloads verified; automatic update pointers unchanged"
exit 0
fi
# Internal RCs retain their immutable record but never move a public
# channel pointer.
if [ "$PRERELEASE" = "true" ] && [ "$CHANNEL" != "preview" ]; then
@@ -1477,7 +1521,7 @@ jobs:
esac
- name: Attach desktop manifest to matching CLI release
if: env.HAS_R2 == 'true' && needs.resolve.outputs.channel != 'preview' && needs.resolve.outputs.prerelease != 'true'
if: env.HAS_R2 == 'true' && needs.resolve.outputs.channel != 'preview' && needs.resolve.outputs.prerelease != 'true' && !inputs.desktop_manual_only
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ needs.resolve.outputs.version }}
+14
View File
@@ -38,6 +38,11 @@ on:
required: false
default: true
type: boolean
desktop_manual_only:
description: "One-time v1.38.8 recovery: unsigned Windows, manual Desktop downloads only"
required: false
default: false
type: boolean
concurrency:
group: stable-release-${{ inputs.tag || github.ref_name }}
@@ -82,6 +87,12 @@ jobs:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
ALLOW_STABLE_RECOVERY: ${{ inputs.allow_recovery }}
run: bash scripts/resolve-stable-release.sh
- name: Restrict manual Desktop recovery
if: ${{ inputs.desktop_manual_only }}
run: |
test "${{ inputs.allow_recovery }}" = "true"
test "${{ steps.release.outputs.cli_tag }}" = "v1.38.8"
test "${{ steps.release.outputs.sha }}" = "7278072720a2dc7a31cce0eec18c1eacc149c0e0"
- name: Revalidate normal release candidate and exact push CI
if: ${{ !inputs.allow_recovery }}
env:
@@ -182,6 +193,7 @@ jobs:
approved_sha: ${{ needs.authorize.outputs.sha }}
orchestrated: true
signing_preflight: true
desktop_manual_only: ${{ inputs.desktop_manual_only || false }}
secrets: inherit
desktop:
@@ -196,6 +208,7 @@ jobs:
approved_sha: ${{ needs.authorize.outputs.sha }}
orchestrated: true
signing_preflight_verified: true
desktop_manual_only: ${{ inputs.desktop_manual_only || false }}
preflight_artifact_prefix: ${{ needs.signpath-preflight.outputs.artifact_prefix }}
secrets: inherit
@@ -247,6 +260,7 @@ jobs:
RELEASE_VERSION: ${{ needs.authorize.outputs.version }}
CLI_TAG: ${{ needs.authorize.outputs.cli_tag }}
DESKTOP_TAG: ${{ needs.authorize.outputs.desktop_tag }}
DESKTOP_MANUAL_ONLY: ${{ inputs.desktop_manual_only || false }}
run: bash scripts/verify-stable-release-artifacts.sh
- name: Publish exact Stable release record
env:
+4 -1
View File
@@ -60,7 +60,10 @@ if ! gh api "$release_endpoint" >"$release_json" 2>"$release_error"; then
exit 1
fi
args=(--title "$title" --notes-file "$notes_file")
if [ "$prerelease" = "true" ]; then
if [ "${DESKTOP_MANUAL_ONLY:-false}" = "true" ]; then
[ "$tag" = "desktop-v1.38.8" ] || { echo "manual exception is restricted to v1.38.8" >&2; exit 1; }
args+=(--latest=false)
elif [ "$prerelease" = "true" ]; then
args+=(--prerelease --latest=false)
else
args+=(--latest)
+26
View File
@@ -13,6 +13,32 @@ trap cleanup EXIT
# Stable tags have one entrypoint and one protected environment. Reusable
# publishers must verify that only that entrypoint can claim prior approval.
# The manual exception is immutable-candidate scoped and cannot advance any
# Desktop update entry point. Keep normal signing as the default.
python3 - "$repo_root" <<'PY'
import pathlib, sys
root = pathlib.Path(sys.argv[1])
stable = (root / '.github/workflows/release-stable.yml').read_text()
desktop = (root / '.github/workflows/release-desktop.yml').read_text()
publisher = (root / 'scripts/publish-desktop-github-release.sh').read_text()
for workflow in (stable, desktop):
block = workflow.split(' desktop_manual_only:', 1)[1].split('\n\n', 1)[0]
assert 'default: false' in block
assert '7278072720a2dc7a31cce0eec18c1eacc149c0e0' in workflow
assert stable.count('desktop_manual_only: ${{ inputs.desktop_manual_only || false }}') == 2
assert "HAS_SIGNPATH: ${{ secrets.SIGNPATH_API_TOKEN != '' && !inputs.desktop_manual_only }}" in desktop
assert desktop.index('name: Validate signing mode') < desktop.index('name: Build and package')
assert "inputs.orchestrated }}\" != \"true\"" in desktop
assert 'manual-download only' in desktop
manual_exit = desktop.index('if [ "$DESKTOP_MANUAL_ONLY" = "true" ]; then', desktop.index('name: Mirror immutable assets'))
assert manual_exit < desktop.index('validate_current_pointer()', manual_exit)
assert 'pointer_moved=false' in desktop[manual_exit:manual_exit + 350]
attach = desktop.split('name: Attach desktop manifest to matching CLI release', 1)[1].split('env:', 1)[0]
assert '!inputs.desktop_manual_only' in attach
manual_publish = publisher.split('if [ "${DESKTOP_MANUAL_ONLY:-false}" = "true" ]; then', 1)[1].split('elif', 1)[0]
assert 'desktop-v1.38.8' in manual_publish and 'args+=(--latest=false)' in manual_publish
assert 'name: Sign artifacts (minisign)' in desktop
PY
[ "$(grep -Ec '^ environment: release$' "$repo_root/.github/workflows/release-stable.yml")" = "1" ]
relay="$repo_root/.github/workflows/release-stable-trigger.yml"
grep -Eq 'actions: write' "$relay"
@@ -50,6 +50,15 @@ jq -e '
all(. as $required | ($names | index($required)) and ($names | index($required + ".minisig")))))
' "$tmp_dir/desktop.json" >/dev/null
if [ "${DESKTOP_MANUAL_ONLY:-false}" = "true" ]; then
[ "$version" = "1.38.8" ] || exit 1
# Both updater entry points must still serve the prior signed release.
gh api "repos/$repository/releases/latest" --jq .tag_name | grep -Fx 'desktop-v1.38.7'
curl -fsSL https://dl.reasonix.io/latest/latest.json > "$tmp_dir/desktop-pointer.json"
jq -e '.version == "v1.38.7"' "$tmp_dir/desktop-pointer.json" >/dev/null
gh release view "$desktop_tag" --repo "$repository" --json body --jq .body | grep -F 'manual-download only'
fi
for attempt in $(seq 1 "$attempts"); do
latest="$(npm view reasonix dist-tags.latest 2>/dev/null || true)"
if [ "$latest" = "$version" ]; then