Simplify R2 to appcast-only (keep DMGs on GitHub) (#2363)
* Simplify R2 upload to appcast-only (keep DMGs on GitHub) DMGs are immutable per-build on GitHub Releases (unique filenames, no overwrite), so there's no race condition for them. Only the appcast.xml needs atomic replacement, which R2 PutObject provides. Upload the original appcast.xml as-is (GitHub Release DMG URLs) to R2. No sed URL rewriting, no DMG uploads, less storage/bandwidth. * Move R2 appcast upload after GitHub Release publish The R2 appcast references GitHub Release DMG URLs, so it must be uploaded after the DMGs exist on GitHub. Previously the R2 upload ran before the publish step, creating a brief window where the appcast pointed to a not-yet-existing DMG. * Add semver guard to stable R2 appcast upload Prevents a backport tag (e.g. v0.62.1 pushed after v0.63.1) from overwriting the stable appcast with an older version. Uses sort -V to compare all non-prerelease tags and only uploads if the current tag is the highest. --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>
This commit is contained in:
parent
f6c949add7
commit
27aab3a035
2 changed files with 54 additions and 68 deletions
59
.github/workflows/nightly.yml
vendored
59
.github/workflows/nightly.yml
vendored
|
|
@ -503,43 +503,6 @@ jobs:
|
||||||
# installs to migrate onto the unified nightly appcast.
|
# installs to migrate onto the unified nightly appcast.
|
||||||
cp appcast.xml appcast-universal.xml
|
cp appcast.xml appcast-universal.xml
|
||||||
|
|
||||||
- name: Upload nightly assets to R2
|
|
||||||
if: needs.decide.outputs.should_publish == 'true' && steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true'
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_DEFAULT_REGION: auto
|
|
||||||
R2_ENDPOINT: "https://${{ secrets.CF_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
command -v aws >/dev/null 2>&1 || { echo "Installing AWS CLI..."; brew install awscli; }
|
|
||||||
BUCKET=cmux-binaries
|
|
||||||
|
|
||||||
# Derive R2 appcast from the GitHub one by replacing the download URL prefix.
|
|
||||||
# EdDSA signature is over the DMG content, not the URL, so this is safe.
|
|
||||||
sed 's|https://github.com/manaflow-ai/cmux/releases/download/nightly/|https://files.cmux.com/nightly/|g' \
|
|
||||||
appcast.xml > appcast-r2.xml
|
|
||||||
|
|
||||||
# Upload immutable versioned DMG (cacheable forever).
|
|
||||||
aws s3 cp "$NIGHTLY_DMG_IMMUTABLE" \
|
|
||||||
"s3://${BUCKET}/nightly/${NIGHTLY_DMG_IMMUTABLE}" \
|
|
||||||
--endpoint-url "$R2_ENDPOINT" \
|
|
||||||
--cache-control "max-age=31536000, immutable"
|
|
||||||
# Upload mutable latest DMG (no cache).
|
|
||||||
aws s3 cp cmux-nightly-macos.dmg \
|
|
||||||
"s3://${BUCKET}/nightly/cmux-nightly-macos.dmg" \
|
|
||||||
--endpoint-url "$R2_ENDPOINT" \
|
|
||||||
--cache-control "no-cache, no-store, must-revalidate"
|
|
||||||
|
|
||||||
# Upload appcast last (atomic PutObject, no 404 window).
|
|
||||||
aws s3 cp appcast-r2.xml \
|
|
||||||
"s3://${BUCKET}/nightly/appcast.xml" \
|
|
||||||
--endpoint-url "$R2_ENDPOINT" \
|
|
||||||
--cache-control "no-cache, no-store, must-revalidate"
|
|
||||||
|
|
||||||
echo "R2 upload complete: https://files.cmux.com/nightly/appcast.xml"
|
|
||||||
|
|
||||||
- name: Attest remote daemon nightly assets
|
- name: Attest remote daemon nightly assets
|
||||||
if: needs.decide.outputs.should_publish != 'true' || (steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true')
|
if: needs.decide.outputs.should_publish != 'true' || (steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true')
|
||||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||||
|
|
@ -598,6 +561,28 @@ jobs:
|
||||||
appcast-universal.xml
|
appcast-universal.xml
|
||||||
overwrite_files: true
|
overwrite_files: true
|
||||||
|
|
||||||
|
- name: Upload nightly appcast to R2
|
||||||
|
if: needs.decide.outputs.should_publish == 'true' && steps.current_head_prebuild.outputs.still_current == 'true' && steps.current_head_postbuild.outputs.still_current == 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: auto
|
||||||
|
R2_ENDPOINT: "https://${{ secrets.CF_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
command -v aws >/dev/null 2>&1 || { echo "Installing AWS CLI..."; brew install awscli; }
|
||||||
|
|
||||||
|
# Upload after GitHub Release publish so the appcast never references
|
||||||
|
# a DMG that doesn't exist yet. R2 PutObject is atomic, so the appcast
|
||||||
|
# is either the old version or the new one, never missing.
|
||||||
|
aws s3 cp appcast.xml \
|
||||||
|
"s3://cmux-binaries/nightly/appcast.xml" \
|
||||||
|
--endpoint-url "$R2_ENDPOINT" \
|
||||||
|
--cache-control "no-cache, no-store, must-revalidate"
|
||||||
|
|
||||||
|
echo "R2 appcast upload complete: https://files.cmux.com/nightly/appcast.xml"
|
||||||
|
|
||||||
- name: Cleanup keychain
|
- name: Cleanup keychain
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
63
.github/workflows/release.yml
vendored
63
.github/workflows/release.yml
vendored
|
|
@ -336,37 +336,6 @@ jobs:
|
||||||
fi
|
fi
|
||||||
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$GITHUB_REF_NAME" appcast.xml
|
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$GITHUB_REF_NAME" appcast.xml
|
||||||
|
|
||||||
- name: Upload release assets to R2
|
|
||||||
if: steps.guard_release_assets.outputs.skip_upload != 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_DEFAULT_REGION: auto
|
|
||||||
R2_ENDPOINT: "https://${{ secrets.CF_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
command -v aws >/dev/null 2>&1 || { echo "Installing AWS CLI..."; brew install awscli; }
|
|
||||||
BUCKET=cmux-binaries
|
|
||||||
|
|
||||||
# Derive R2 appcast by replacing the download URL prefix.
|
|
||||||
sed "s|https://github.com/manaflow-ai/cmux/releases/download/${GITHUB_REF_NAME}/|https://files.cmux.com/stable/|g" \
|
|
||||||
appcast.xml > appcast-r2.xml
|
|
||||||
|
|
||||||
# Upload DMG first so the appcast never references a missing file.
|
|
||||||
aws s3 cp cmux-macos.dmg \
|
|
||||||
"s3://${BUCKET}/stable/cmux-macos.dmg" \
|
|
||||||
--endpoint-url "$R2_ENDPOINT" \
|
|
||||||
--cache-control "no-cache, no-store, must-revalidate"
|
|
||||||
|
|
||||||
# Upload appcast last (atomic PutObject, no 404 window).
|
|
||||||
aws s3 cp appcast-r2.xml \
|
|
||||||
"s3://${BUCKET}/stable/appcast.xml" \
|
|
||||||
--endpoint-url "$R2_ENDPOINT" \
|
|
||||||
--cache-control "no-cache, no-store, must-revalidate"
|
|
||||||
|
|
||||||
echo "R2 upload complete: https://files.cmux.com/stable/appcast.xml"
|
|
||||||
|
|
||||||
- name: Attest remote daemon release assets
|
- name: Attest remote daemon release assets
|
||||||
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
if: steps.guard_release_assets.outputs.skip_all != 'true'
|
||||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||||
|
|
@ -406,6 +375,38 @@ jobs:
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
overwrite_files: false
|
overwrite_files: false
|
||||||
|
|
||||||
|
- name: Upload release appcast to R2
|
||||||
|
if: steps.guard_release_assets.outputs.skip_upload != 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||||
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||||
|
AWS_DEFAULT_REGION: auto
|
||||||
|
R2_ENDPOINT: "https://${{ secrets.CF_R2_ACCOUNT_ID }}.r2.cloudflarestorage.com"
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
command -v aws >/dev/null 2>&1 || { echo "Installing AWS CLI..."; brew install awscli; }
|
||||||
|
|
||||||
|
# Guard: only upload if this tag is the highest semver release.
|
||||||
|
# Prevents a backport tag (e.g. v0.62.1 after v0.63.1) from
|
||||||
|
# overwriting the stable appcast with an older version.
|
||||||
|
LATEST=$(gh release list --exclude-drafts --exclude-pre-releases \
|
||||||
|
--json tagName -q '.[].tagName' | sort -V | tail -1)
|
||||||
|
if [ -n "$LATEST" ] && [ "$LATEST" != "$GITHUB_REF_NAME" ]; then
|
||||||
|
echo "Skipping R2 stable upload: $GITHUB_REF_NAME is not the latest release ($LATEST)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload after GitHub Release publish so the appcast never references
|
||||||
|
# a DMG that doesn't exist yet. R2 PutObject is atomic, so the appcast
|
||||||
|
# is either the old version or the new one, never missing.
|
||||||
|
aws s3 cp appcast.xml \
|
||||||
|
"s3://cmux-binaries/stable/appcast.xml" \
|
||||||
|
--endpoint-url "$R2_ENDPOINT" \
|
||||||
|
--cache-control "no-cache, no-store, must-revalidate"
|
||||||
|
|
||||||
|
echo "R2 appcast upload complete: https://files.cmux.com/stable/appcast.xml"
|
||||||
|
|
||||||
- name: Cleanup keychain
|
- name: Cleanup keychain
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue