Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 55 additions & 51 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ jobs:
- name: Update MoonBit package registry
run: moon update

# The publish script mirrors release artifacts to OSS with ossutil 2.x,
# The publish script uploads release artifacts to OSS with ossutil 2.x,
# pinned and installed from the official distribution.
- name: Install ossutil
shell: bash
Expand Down Expand Up @@ -246,8 +246,9 @@ jobs:
--sign "$MACOS_SIGNING_IDENTITY" \
--notarize "$NOTARY_PROFILE"

# Keep one compressed browser archive beside the desktop artifacts. The
# API extracts this exact archive when the release is published.
# Keep one compressed Browser archive beside the Desktop artifacts. OSS
# stores it as an immutable release artifact; the API receives the same
# small archive only because `/console/` must remain on the API origin.
- name: Build browser release artifact
shell: bash
run: |
Expand All @@ -260,6 +261,23 @@ jobs:
-czf desktop/dist/SeekMoon.browser.tar.gz \
browser

# Upload artifacts before publication. Until API publish succeeds, a
# retry may replace nondeterministic rebuilds; a published Browser
# version restores the exact OSS bytes instead.
- name: Upload release to OSS
shell: bash
env:
OPENSEEK_DEPLOY_TOKEN: ${{ secrets.OPENSEEK_DEPLOY_TOKEN }}
OPENSEEK_OSS_BUCKET: ${{ secrets.OPENSEEK_OSS_BUCKET }}
OPENSEEK_OSS_REGION: ${{ secrets.OPENSEEK_OSS_REGION }}
OSS_ACCESS_KEY_ID: ${{ secrets.OPENSEEK_OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OPENSEEK_OSS_ACCESS_KEY_SECRET }}
run: |
desktop/scripts/publish-release.sh upload "v$RELEASE_VERSION"

# A retry of an API-published version replaces this run's nondeterministic
# rebuilds with the exact OSS objects before reaching here. Keep those
# canonical bytes as the downloadable Actions artifact too.
- name: Upload release artifacts
uses: actions/upload-artifact@v4
with:
Expand All @@ -271,8 +289,9 @@ jobs:
if-no-files-found: error
retention-days: 14

# Every artifact is uploaded once. The same publish request regenerates
# latest.json and extracts/selects the browser archive on the server.
# Publish sends filenames and digests to the API. The API selects the
# staged Browser version and atomically replaces its own latest.json; it
# does not receive the ZIP or DMG.
- name: Publish release
shell: bash
env:
Expand All @@ -282,15 +301,6 @@ jobs:
OSS_ACCESS_KEY_ID: ${{ secrets.OPENSEEK_OSS_ACCESS_KEY_ID }}
OSS_ACCESS_KEY_SECRET: ${{ secrets.OPENSEEK_OSS_ACCESS_KEY_SECRET }}
run: |
desktop/scripts/publish-release.sh upload \
desktop/dist/SeekMoon.app.zip \
macos-arm64
desktop/scripts/publish-release.sh upload \
desktop/dist/SeekMoon.dmg \
macos-arm64-dmg
desktop/scripts/publish-release.sh upload \
desktop/dist/SeekMoon.browser.tar.gz \
browser
desktop/scripts/publish-release.sh publish "v$RELEASE_VERSION"

- name: Verify release manifest
Expand All @@ -301,45 +311,39 @@ jobs:
jq -e --arg version "$RELEASE_VERSION" '.version == $version' \
<<< "$manifest"

archive_sha256="$(shasum -a 256 desktop/dist/SeekMoon.app.zip | cut -d' ' -f1)"
dmg_sha256="$(shasum -a 256 desktop/dist/SeekMoon.dmg | cut -d' ' -f1)"
browser_sha256="$(shasum -a 256 desktop/dist/SeekMoon.browser.tar.gz | cut -d' ' -f1)"

# The manifest is the client contract: whatever URL it names for a
# platform is what the updater and the download page fetch. URLs
# are read back rather than reconstructed, so the same check holds
# whether the server's releases base URL points at this API or at
# the OSS-backed CDN — each entry's digest must match the artifact
# built here, and so must the bytes its URL actually serves.
platform_url() {
jq -er --arg key "$1" '.platforms[$key].url' <<< "$manifest"
}
verify_platform() {
local key="$1" sha256="$2"
local url manifest_sha256 served served_sha256
url="$(platform_url "$key")"
platforms=("macos-arm64" "macos-arm64-dmg" "browser")
artifacts=(
"desktop/dist/SeekMoon.app.zip"
"desktop/dist/SeekMoon.dmg"
"desktop/dist/SeekMoon.browser.tar.gz"
)
for index in 0 1 2; do
key="${platforms[$index]}"
artifact="${artifacts[$index]}"
url="$(jq -er --arg key "$key" '.platforms[$key].url' <<< "$manifest")"
manifest_sha256="$(jq -er --arg key "$key" '.platforms[$key].sha256' <<< "$manifest")"
if [[ "$manifest_sha256" != "$sha256" ]]; then
echo "::error::Manifest sha256 for $key does not match the built artifact"
exit 1
fi
served="$RUNNER_TEMP/openseek-release-$key"
curl -fsSL --retry 3 -o "$served" "$url"
served_sha256="$(shasum -a 256 "$served" | cut -d' ' -f1)"
rm -f "$served"
if [[ "$served_sha256" != "$sha256" ]]; then
echo "::error::Bytes served for $key at $url do not match the built artifact"
local_sha256="$(shasum -a 256 "$artifact" | cut -d' ' -f1)"
local_size="$(wc -c < "$artifact" | tr -d '[:space:]')"
headers="$(curl -fsSI --retry 3 "$url" | tr -d '\r')"
served_size="$(awk 'tolower($1) == "content-length:" { print $2; exit }' <<< "$headers")"
served_sha256="$(awk 'tolower($1) == "x-oss-meta-sha256:" { print $2; exit }' <<< "$headers")"
served_crc64="$(awk 'tolower($1) == "x-oss-hash-crc64ecma:" { print $2; exit }' <<< "$headers")"
if [[ "$manifest_sha256" != "$local_sha256" || \
"$served_sha256" != "$local_sha256" || \
"$served_size" != "$local_size" || \
-z "$served_crc64" ]]; then
echo "::error::OSS metadata for $key does not match the built artifact"
exit 1
fi
echo "$key ok: $url"
}
done

# The updater installs from the bare macOS platform key. The DMG and
# browser bundle are separate release artifacts for manual install
# and later browser deployment respectively.
verify_platform macos-arm64 "$archive_sha256"
verify_platform macos-arm64-dmg "$dmg_sha256"
verify_platform browser "$browser_sha256"
archive_sha256="$(shasum -a 256 desktop/dist/SeekMoon.app.zip | cut -d' ' -f1)"
dmg_sha256="$(shasum -a 256 desktop/dist/SeekMoon.dmg | cut -d' ' -f1)"
browser_sha256="$(shasum -a 256 desktop/dist/SeekMoon.browser.tar.gz | cut -d' ' -f1)"
archive_url="$(jq -er '.platforms["macos-arm64"].url' <<< "$manifest")"
dmg_url="$(jq -er '.platforms["macos-arm64-dmg"].url' <<< "$manifest")"
browser_url="$(jq -er '.platforms.browser.url' <<< "$manifest")"

browser_manifest="$(curl -fsSL "$OPENSEEK_API_ORIGIN/browser/releases/current.json")"
browser_base_url="$OPENSEEK_API_ORIGIN/console/releases/v$RELEASE_VERSION"
Expand Down Expand Up @@ -373,11 +377,11 @@ jobs:
echo "## Desktop $RELEASE_CHANNEL release"
echo
echo "- Version: \`$RELEASE_VERSION\`"
echo "- Updater ZIP: $(platform_url macos-arm64)"
echo "- Updater ZIP: $archive_url"
echo "- ZIP SHA-256: \`$archive_sha256\`"
echo "- Installer DMG: $(platform_url macos-arm64-dmg)"
echo "- Installer DMG: $dmg_url"
echo "- DMG SHA-256: \`$dmg_sha256\`"
echo "- Browser bundle: $(platform_url browser)"
echo "- Browser bundle: $browser_url"
echo "- Browser SHA-256: \`$browser_sha256\`"
echo "- Browser console: $browser_base_url/"
} >> "$GITHUB_STEP_SUMMARY"
Expand Down
2 changes: 1 addition & 1 deletion desktop/frontend/browser/main.mbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// The browser shell: the console bundle openseek-api serves at `/`
// The browser shell: the console bundle openseek-api serves under `/console/`
// (its Dockerfile builds this package and ships `browser.js` with the
// sibling `index.html`). It shares everything with the desktop shell
// today; console-only features will migrate here as the core splits.
Expand Down
3 changes: 2 additions & 1 deletion desktop/package/browser/main.mbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
///|
/// The complete browser application directory consumed by openseek-api.
/// The complete Browser application directory. Release CI archives this for
/// openseek-api's same-origin `/console/` deployment.
const BrowserDistDir : String = "dist/browser"

///|
Expand Down
Loading
Loading