ci: publish darwin runtime + split >2GiB offline packages
A) runtime-release.yaml: add a native build-darwin job (macos-14 arm64 /
macos-13 amd64) that builds the dashboard + cross-correct Go API and
publishes xworkspace-console-runtime-darwin-{arm64,amd64}.tar.gz, fixing the
macOS deploy 404. publish now needs both build jobs and globs all runtimes.
B) offline-package workflow: GitHub caps release assets at 2 GiB. Split any
package >= 2 GiB into 1900 MiB parts plus a <name>.parts manifest and upload
the parts. The offline bootstrap (download_offline_split) falls back to the
manifest and reassembles the parts when the single asset is absent. Verified
the split/reassemble round-trips byte-for-byte.
This commit is contained in:
parent
a7c6e41f89
commit
77230a5fd4
@ -248,28 +248,67 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
shopt -s nullglob
|
||||
|
||||
# GitHub release assets are hard-capped at 2 GiB. Packages at/over the
|
||||
# limit are split into <2 GiB parts plus a ".parts" manifest; the
|
||||
# offline bootstrap reassembles them. Parts are 1900 MiB.
|
||||
GH_ASSET_LIMIT=2147483648
|
||||
SPLIT_SIZE=1900m
|
||||
|
||||
delete_asset_if_present() {
|
||||
local name=$1
|
||||
if gh release view "$TAG_NAME" --json assets --jq '.assets[].name' \
|
||||
2>/dev/null | grep -Fxq "$name"; then
|
||||
echo "Deleting existing release asset ${name}"
|
||||
gh release delete-asset "$TAG_NAME" "$name" --yes
|
||||
fi
|
||||
}
|
||||
|
||||
upload_one() {
|
||||
local file=$1 name attempt
|
||||
name="$(basename "$file")"
|
||||
delete_asset_if_present "$name"
|
||||
for attempt in 1 2 3; do
|
||||
if gh release upload "$TAG_NAME" "$file" --clobber; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$attempt" -eq 3 ]]; then
|
||||
echo "Failed to upload ${file} after ${attempt} attempts" >&2
|
||||
return 1
|
||||
fi
|
||||
sleep $((attempt * 20))
|
||||
done
|
||||
}
|
||||
|
||||
packages=(release-artifacts/*.tar.gz)
|
||||
if [[ ${#packages[@]} -eq 0 ]]; then
|
||||
echo "No offline packages found in release-artifacts" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for package in "${packages[@]}"; do
|
||||
echo "Uploading ${package}"
|
||||
asset_name="$(basename "$package")"
|
||||
if gh release view "$TAG_NAME" --json assets --jq '.assets[].name' | grep -Fxq "$asset_name"; then
|
||||
echo "Deleting existing release asset ${asset_name} before upload"
|
||||
gh release delete-asset "$TAG_NAME" "$asset_name" --yes
|
||||
size="$(stat -c%s "$package")"
|
||||
if [[ "$size" -lt "$GH_ASSET_LIMIT" ]]; then
|
||||
echo "Uploading ${asset_name} (${size} bytes)"
|
||||
upload_one "$package"
|
||||
else
|
||||
echo "Splitting oversized ${asset_name} (${size} bytes) into ${SPLIT_SIZE} parts"
|
||||
dir="$(dirname "$package")"
|
||||
(
|
||||
cd "$dir"
|
||||
rm -f "${asset_name}".part-* "${asset_name}.parts"
|
||||
split -b "$SPLIT_SIZE" -d -a 3 "$asset_name" "${asset_name}.part-"
|
||||
ls "${asset_name}".part-* | LC_ALL=C sort > "${asset_name}.parts"
|
||||
)
|
||||
# Remove any stale whole asset, then publish the manifest + parts.
|
||||
delete_asset_if_present "$asset_name"
|
||||
upload_one "${dir}/${asset_name}.parts"
|
||||
for part in "${dir}/${asset_name}".part-*; do
|
||||
echo "Uploading $(basename "$part")"
|
||||
upload_one "$part"
|
||||
done
|
||||
fi
|
||||
for attempt in 1 2 3; do
|
||||
if gh release upload "$TAG_NAME" "$package" --clobber; then
|
||||
break
|
||||
fi
|
||||
if [[ "$attempt" -eq 3 ]]; then
|
||||
echo "Failed to upload ${package} after ${attempt} attempts" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep $((attempt * 20))
|
||||
done
|
||||
done
|
||||
|
||||
- name: Rsync packages to remote mirror
|
||||
|
||||
80
.github/workflows/runtime-release.yaml
vendored
80
.github/workflows/runtime-release.yaml
vendored
@ -93,14 +93,90 @@ jobs:
|
||||
dist/assets/SHA256SUMS-*
|
||||
if-no-files-found: error
|
||||
|
||||
build-darwin:
|
||||
name: Build darwin-${{ matrix.arch }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64
|
||||
runner: macos-14
|
||||
- arch: amd64
|
||||
runner: macos-13
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: api/go.mod
|
||||
cache: false
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
- name: Build target runtime
|
||||
env:
|
||||
TARGET_ARCH: ${{ matrix.arch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
root="dist/runtime/xworkspace-console"
|
||||
mkdir -p "${root}/dashboard" "${root}/bin" "dist/assets"
|
||||
|
||||
cp -a scripts "${root}/"
|
||||
cp dashboard/package.json dashboard/package-lock.json dashboard/index.html \
|
||||
dashboard/tsconfig.json dashboard/vite.config.ts "${root}/dashboard/"
|
||||
cp -a dashboard/src "${root}/dashboard/"
|
||||
|
||||
# Native macOS build: npm + go run on the matching arch runner so the
|
||||
# dashboard node_modules and the API binary are genuinely darwin/${TARGET_ARCH}.
|
||||
(
|
||||
cd dashboard
|
||||
npm ci --no-audit --no-fund
|
||||
npm run build
|
||||
)
|
||||
cp -a dashboard/dist dashboard/node_modules "${root}/dashboard/"
|
||||
|
||||
(
|
||||
cd api
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH="${TARGET_ARCH}" \
|
||||
go build -buildvcs=false -trimpath -o "../${root}/bin/xworkspace-api" .
|
||||
)
|
||||
|
||||
cat > "${root}/manifest.json" <<JSON
|
||||
{
|
||||
"component": "xworkspace-console",
|
||||
"commit": "${GITHUB_SHA}",
|
||||
"os": "darwin",
|
||||
"arch": "${TARGET_ARCH}",
|
||||
"apiBinary": "bin/xworkspace-api",
|
||||
"dashboard": "dashboard"
|
||||
}
|
||||
JSON
|
||||
tar -czf "dist/assets/xworkspace-console-runtime-darwin-${TARGET_ARCH}.tar.gz" \
|
||||
-C "dist/runtime" xworkspace-console
|
||||
(
|
||||
cd dist/assets
|
||||
shasum -a 256 -- ./*.tar.gz | sed 's# \./# #' > "SHA256SUMS-darwin-${TARGET_ARCH}"
|
||||
)
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: xworkspace-console-runtime-darwin-${{ matrix.arch }}
|
||||
path: |
|
||||
dist/assets/*.tar.gz
|
||||
dist/assets/SHA256SUMS-*
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
name: Publish runtime release
|
||||
needs: build
|
||||
needs: [build, build-darwin]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: xworkspace-console-runtime-linux-*
|
||||
pattern: xworkspace-console-runtime-*
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
|
||||
@ -622,6 +622,32 @@ validate_offline_archive() {
|
||||
return 1
|
||||
}
|
||||
|
||||
download_offline_split() {
|
||||
# Reassemble an offline package that was published as <2 GiB parts because it
|
||||
# exceeded GitHub's 2 GiB asset cap. "$source" is the would-be single-file
|
||||
# URL; the parts manifest lives at "${source}.parts" and lists the part asset
|
||||
# names (one per line), which sit next to it in the same release.
|
||||
local source=$1
|
||||
local partial=$2
|
||||
local base manifest part part_url
|
||||
base="${source%/*}"
|
||||
manifest="${partial}.parts"
|
||||
curl -fL --retry 3 --retry-delay 5 -o "$manifest" "${source}.parts" 2>/dev/null || return 1
|
||||
: > "$partial"
|
||||
while IFS= read -r part; do
|
||||
part="${part%$'\r'}"
|
||||
[ -n "$part" ] || continue
|
||||
part_url="${base}/${part}"
|
||||
info "Downloading AI Workspace offline package part: ${part}"
|
||||
if ! curl -fL --retry 3 --retry-delay 5 "$part_url" >> "$partial"; then
|
||||
rm -f "$manifest"
|
||||
return 1
|
||||
fi
|
||||
done < "$manifest"
|
||||
rm -f "$manifest"
|
||||
return 0
|
||||
}
|
||||
|
||||
prepare_offline_package_root() {
|
||||
local source=$1
|
||||
local filename=$2
|
||||
@ -640,9 +666,12 @@ prepare_offline_package_root() {
|
||||
info "Reusing cached AI Workspace offline package: $package"
|
||||
else
|
||||
info "Downloading AI Workspace offline package: $resolved_source"
|
||||
if ! curl -fL --retry 3 --retry-delay 5 --continue-at - -o "$partial" "$resolved_source"; then
|
||||
if ! curl -fL --retry 3 --retry-delay 5 --continue-at - -o "$partial" "$resolved_source" 2>/dev/null \
|
||||
&& ! curl -fL --retry 3 --retry-delay 5 -o "$partial" "$resolved_source" 2>/dev/null; then
|
||||
# The single asset may have been split into <2 GiB parts.
|
||||
rm -f "$partial"
|
||||
curl -fL --retry 3 --retry-delay 5 -o "$partial" "$resolved_source" || return 1
|
||||
info "Single offline asset unavailable; trying split parts..."
|
||||
download_offline_split "$source" "$partial" || return 1
|
||||
fi
|
||||
validate_offline_archive "$partial" || return 1
|
||||
mv "$partial" "$package"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user