diff --git a/.github/workflows/evaos-beta-local-signed-dmg-manifest.yml b/.github/workflows/evaos-beta-local-signed-dmg-manifest.yml new file mode 100644 index 0000000000..942f3343a4 --- /dev/null +++ b/.github/workflows/evaos-beta-local-signed-dmg-manifest.yml @@ -0,0 +1,234 @@ +name: Register evaOS Beta Local-Signed DMG Manifest + +on: + workflow_dispatch: + inputs: + local_signed_dmg_fallback_ack: + description: 'Type evaos-local-signed-dmg to acknowledge these release assets were locally Developer ID signed, notarized, stapled, and Gatekeeper validated.' + required: true + type: string + tag: + description: 'Existing non-dev evaOS beta release tag whose finalized assets are already attached to the GitHub prerelease.' + required: true + type: string + source_ci_run_id: + description: 'Build and Release run id that produced the staged macOS artifacts.' + required: true + type: string + source_ci_head_sha: + description: 'Commit SHA used by the staged Build and Release run.' + required: true + type: string + source_ci_conclusion: + description: 'Conclusion of the staged Build and Release run.' + required: true + default: failure + type: choice + options: + - failure + - cancelled + - success + source_ci_head_branch: + description: 'Branch used by the staged Build and Release run.' + required: true + default: evaos/beta-rc-20260612 + type: string + source_artifact_names: + description: 'Comma-separated source artifact names from the staged run. Use macos-build-arm64 for macos-arm64 releases.' + required: true + default: macos-build-arm64 + type: string + fallback_reason: + description: 'Concrete reason for local DMG finalization.' + required: true + default: ci-dmg-codesign-timeout + type: string + local_finalization_proof_ref: + description: 'Non-secret issue, run, or artifact reference containing local signing/notarization/stapling proof.' + required: true + type: string + dmg_notary_submission_ids: + description: 'Comma-separated Apple notary submission ids for the finalized DMGs.' + required: true + type: string + release_target_platforms: + description: 'Release target platforms for asset verification. Use macos-arm64 on beta RC refs that include arm64-only verification support.' + required: false + default: macos-arm64 + type: choice + options: + - macos + - macos-arm64 + - all + +concurrency: + group: evaos-beta-local-signed-dmg-manifest-${{ github.event.inputs.tag || github.run_id }} + cancel-in-progress: false + +permissions: + actions: read + contents: read + +env: + EVAOS_RELEASE_TARGET_PLATFORMS: ${{ inputs.release_target_platforms || 'macos-arm64' }} + +jobs: + register-manifest: + name: Register trusted local-signed DMG manifest + runs-on: ubuntu-latest + permissions: + actions: read + # Required only for attaching the trusted manifest back to the GitHub prerelease. + contents: write + steps: + - name: Validate inputs + env: + ACK: ${{ github.event.inputs.local_signed_dmg_fallback_ack }} + TAG: ${{ github.event.inputs.tag }} + PROOF_REF: ${{ github.event.inputs.local_finalization_proof_ref }} + run: | + set -euo pipefail + if [ "$ACK" != "evaos-local-signed-dmg" ]; then + echo "::error::Wrong acknowledgement. Type evaos-local-signed-dmg." + exit 1 + fi + if [[ "$TAG" != evaos-beta-* ]]; then + echo "::error::Refusing non-evaOS beta tag: $TAG" + exit 1 + fi + if [[ ! "$TAG" =~ ^evaos-beta-[A-Za-z0-9._-]+$ ]]; then + echo "::error::Release tag contains unsupported characters: $TAG" + exit 1 + fi + if [[ "$TAG" == *"-dev" || "$TAG" == *"-dev-"* || "$TAG" == *"-dev."* ]]; then + echo "::error::Refusing development beta tag: $TAG" + exit 1 + fi + if [ ${#PROOF_REF} -lt 8 ] || [[ "$PROOF_REF" == *"Bearer "* ]] || [[ "$PROOF_REF" == *"token"* ]]; then + echo "::error::local_finalization_proof_ref must be a concrete non-secret reference." + exit 1 + fi + + - name: Checkout release refs + uses: actions/checkout@v6 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Download finalized release assets + env: + GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} + REPOSITORY: ${{ github.repository }} + TAG: ${{ github.event.inputs.tag }} + run: | + set -euo pipefail + mkdir -p release-assets + case "${EVAOS_RELEASE_TARGET_PLATFORMS}" in + macos) + patterns=( + --pattern "*.dmg" + --pattern "*.zip" + --pattern "latest-mac.yml" + --pattern "latest-arm64-mac.yml" + ) + ;; + macos-arm64) + patterns=( + --pattern "*arm64*.dmg" + --pattern "*arm64*.zip" + --pattern "latest-arm64-mac.yml" + ) + ;; + all) + patterns=( + --pattern "*.dmg" + --pattern "*.exe" + --pattern "*.msi" + --pattern "*.deb" + --pattern "*.zip" + --pattern "*.yml" + ) + ;; + *) + echo "::error::Unsupported EVAOS_RELEASE_TARGET_PLATFORMS: ${EVAOS_RELEASE_TARGET_PLATFORMS}" + exit 1 + ;; + esac + gh release download "$TAG" \ + --repo "$REPOSITORY" \ + --dir release-assets \ + "${patterns[@]}" + rm -f release-assets/evaos-beta-release-manifest.json + ls -la release-assets + + - name: Verify finalized release assets + env: + INCLUDE_WEB_CLI_ASSETS: '0' + MOCK_PRODUCT_NAME: evaOS Workbench + TAG_NAME: ${{ github.event.inputs.tag }} + run: | + set -euo pipefail + VERSION="${TAG_NAME#evaos-beta-}" + VERSION="${VERSION#v}" + INCLUDE_WEB_CLI_ASSETS=0 MOCK_VERSION="$VERSION" bash scripts/verify-release-assets.sh release-assets + + - name: Write local-signed fallback manifest + env: + TAG_NAME: ${{ github.event.inputs.tag }} + GITHUB_REPOSITORY: ${{ github.repository }} + EVAOS_BETA_RELEASE_WORKFLOW: Build and Release + EVAOS_BETA_RELEASE_PUBLISH_ENABLED: 'true' + EVAOS_BETA_RELEASE_PROVENANCE_MODE: local-signed-dmg-fallback + EVAOS_BETA_RELEASE_COMMIT: ${{ github.event.inputs.source_ci_head_sha }} + EVAOS_BETA_RELEASE_BRANCH: ${{ github.event.inputs.source_ci_head_branch }} + EVAOS_BETA_LOCAL_DMG_SOURCE_RUN_ID: ${{ github.event.inputs.source_ci_run_id }} + EVAOS_BETA_LOCAL_DMG_SOURCE_WORKFLOW: Build and Release + EVAOS_BETA_LOCAL_DMG_SOURCE_CONCLUSION: ${{ github.event.inputs.source_ci_conclusion }} + EVAOS_BETA_LOCAL_DMG_SOURCE_SHA: ${{ github.event.inputs.source_ci_head_sha }} + EVAOS_BETA_LOCAL_DMG_SOURCE_BRANCH: ${{ github.event.inputs.source_ci_head_branch }} + EVAOS_BETA_LOCAL_DMG_SOURCE_ARTIFACTS: ${{ github.event.inputs.source_artifact_names }} + EVAOS_BETA_LOCAL_DMG_FALLBACK_REASON: ${{ github.event.inputs.fallback_reason }} + EVAOS_BETA_LOCAL_DMG_FINALIZATION_PROOF_REF: ${{ github.event.inputs.local_finalization_proof_ref }} + EVAOS_BETA_LOCAL_DMG_NOTARY_SUBMISSION_IDS: ${{ github.event.inputs.dmg_notary_submission_ids }} + run: node scripts/evaosBetaReleaseGate.js write-manifest release-assets "$TAG_NAME" + + - name: Verify local-signed fallback manifest + env: + GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + EXPECTED_RELEASE_COMMIT: ${{ github.event.inputs.source_ci_head_sha }} + EVAOS_BETA_LOCAL_SIGNED_DMG_FALLBACK_ACK: ${{ github.event.inputs.local_signed_dmg_fallback_ack }} + TAG_NAME: ${{ github.event.inputs.tag }} + run: node scripts/evaosBetaReleaseGate.js verify-manifest release-assets "$TAG_NAME" + + - name: Upload trusted manifest artifact + uses: actions/upload-artifact@v4 + with: + name: evaos-beta-release-manifest-${{ github.event.inputs.tag }} + path: release-assets/evaos-beta-release-manifest.json + if-no-files-found: error + retention-days: 90 + + - name: Attach manifest to GitHub prerelease + env: + GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} + REPOSITORY: ${{ github.repository }} + TAG: ${{ github.event.inputs.tag }} + run: | + set -euo pipefail + gh release upload "$TAG" release-assets/evaos-beta-release-manifest.json \ + --repo "$REPOSITORY" \ + --clobber + + - name: Summary + env: + RUN_ID: ${{ github.run_id }} + TAG: ${{ github.event.inputs.tag }} + run: | + { + echo "## Registered trusted local-signed DMG manifest" + echo "" + echo "- Tag: \`$TAG\`" + echo "- Manifest artifact: \`evaos-beta-release-manifest-$TAG\`" + echo "- Next: run evaOS Beta RC Canary with \`trusted_manifest_run_id=$RUN_ID\` and \`local_signed_dmg_fallback_ack=evaos-local-signed-dmg\`." + } >> "$GITHUB_STEP_SUMMARY" diff --git a/scripts/verify-release-assets.sh b/scripts/verify-release-assets.sh index 85812e44e0..38ae97cf3e 100644 --- a/scripts/verify-release-assets.sh +++ b/scripts/verify-release-assets.sh @@ -12,11 +12,11 @@ ERRORS=0 shopt -s nullglob case "$RELEASE_TARGET_PLATFORMS" in - all|macos) + all|macos|macos-arm64) ;; *) echo "FAIL: unsupported EVAOS_RELEASE_TARGET_PLATFORMS: $RELEASE_TARGET_PLATFORMS" - echo "FAIL: supported values: all, macos" + echo "FAIL: supported values: all, macos, macos-arm64" exit 1 ;; esac @@ -25,6 +25,13 @@ echo "Release target platforms: $RELEASE_TARGET_PLATFORMS" assert_evaos_beta_asset_identity() { local base="$1" + local base_lc expected_name expected_asset expected_compact expected_slug + + base_lc="$(printf '%s' "$base" | tr '[:upper:]' '[:lower:]')" + expected_name="$(printf '%s' "$MOCK_PRODUCT_NAME" | tr '[:upper:]' '[:lower:]')" + expected_asset="$(printf '%s' "$MOCK_PRODUCT_ASSET_NAME" | tr '[:upper:]' '[:lower:]')" + expected_compact="${expected_name// /}" + expected_slug="${expected_name// /-}" case "$base" in *"AionUi"*|*"AionUI"*|*"Aion-UI"*|*"aion-ui"*|*"aionui"*) @@ -34,11 +41,11 @@ assert_evaos_beta_asset_identity() { ;; esac - case "$base" in - *"evaOS Workbench Beta"*|*"evaOS.Workbench.Beta"*|*"EvaOSWorkbenchBeta"*|*"evaos-workbench-beta"*) + case "$base_lc" in + *"$expected_name"*|*"$expected_asset"*|*"$expected_compact"*|*"$expected_slug"*) ;; *) - echo "FAIL: beta asset lacks evaOS identity marker: $base" + echo "FAIL: beta asset lacks expected evaOS identity marker ($MOCK_PRODUCT_NAME): $base" ERRORS=$((ERRORS + 1)) ;; esac @@ -47,6 +54,8 @@ assert_evaos_beta_asset_identity() { REQUIRED_METADATA=(latest-mac.yml) if [ "$RELEASE_TARGET_PLATFORMS" = "all" ]; then REQUIRED_METADATA=(latest.yml latest-mac.yml latest-linux.yml latest-linux-arm64.yml) +elif [ "$RELEASE_TARGET_PLATFORMS" = "macos-arm64" ]; then + REQUIRED_METADATA=(latest-arm64-mac.yml) fi for f in "${REQUIRED_METADATA[@]}"; do @@ -95,7 +104,9 @@ assert_metadata_points_to_existing_file() { echo "PASS: $metadata_name -> $ref_file" } -assert_metadata_points_to_existing_file "latest-mac.yml" "(mac-x64|darwin-x64|x64)" +if [ "$RELEASE_TARGET_PLATFORMS" != "macos-arm64" ]; then + assert_metadata_points_to_existing_file "latest-mac.yml" "(mac-x64|darwin-x64|x64)" +fi if [ "$RELEASE_TARGET_PLATFORMS" = "all" ]; then assert_metadata_points_to_existing_file "latest.yml" "(win-x64|win32-x64|x64)" assert_metadata_points_to_existing_file "latest-linux.yml" "(linux|AppImage|deb)" @@ -144,6 +155,8 @@ assert_required_glob() { if [ "$RELEASE_TARGET_PLATFORMS" = "macos" ]; then assert_required_glob "macOS x64 DMG" "${MOCK_PRODUCT_ASSET_NAME}-*-mac-x64.dmg" assert_required_glob "macOS arm64 DMG" "${MOCK_PRODUCT_ASSET_NAME}-*-mac-arm64.dmg" +elif [ "$RELEASE_TARGET_PLATFORMS" = "macos-arm64" ]; then + assert_required_glob "macOS arm64 DMG" "${MOCK_PRODUCT_ASSET_NAME}-*-mac-arm64.dmg" else REQUIRED_DISTRIBUTABLES=( "${MOCK_PRODUCT_ASSET_NAME}-${MOCK_VERSION}-win-x64.exe" @@ -164,7 +177,7 @@ else done fi -if [ "$RELEASE_TARGET_PLATFORMS" = "macos" ]; then +if [ "$RELEASE_TARGET_PLATFORMS" = "macos" ] || [ "$RELEASE_TARGET_PLATFORMS" = "macos-arm64" ]; then DEFERRED_PLATFORM_FILES=( "$OUTPUT_DIR"/*.exe "$OUTPUT_DIR"/*.msi @@ -173,6 +186,9 @@ if [ "$RELEASE_TARGET_PLATFORMS" = "macos" ]; then "$OUTPUT_DIR"/latest-win-arm64.yml "$OUTPUT_DIR"/latest-linux*.yml ) + if [ "$RELEASE_TARGET_PLATFORMS" = "macos-arm64" ]; then + DEFERRED_PLATFORM_FILES+=("$OUTPUT_DIR"/latest-mac.yml) + fi for f in "${DEFERRED_PLATFORM_FILES[@]}"; do [ -e "$f" ] || continue echo "FAIL: macOS release profile contains deferred Windows/Linux asset or metadata: $(basename "$f")" @@ -189,6 +205,8 @@ if [ "$INCLUDE_WEB_CLI_ASSETS" = "1" ]; then # Web-CLI tarballs + checksums if [ "$RELEASE_TARGET_PLATFORMS" = "macos" ]; then WEB_PLATFORMS=(darwin-arm64 darwin-x86_64) + elif [ "$RELEASE_TARGET_PLATFORMS" = "macos-arm64" ]; then + WEB_PLATFORMS=(darwin-arm64) else WEB_PLATFORMS=(darwin-arm64 darwin-x86_64 linux-arm64 linux-x86_64 win-x86_64) fi