Finalize Notarization #634
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Finalize Notarization | |
| on: | |
| schedule: | |
| - cron: "*/30 * * * *" | |
| workflow_dispatch: | |
| permissions: | |
| actions: write | |
| contents: write | |
| concurrency: | |
| group: finalize-notarization | |
| cancel-in-progress: false | |
| jobs: | |
| poll: | |
| runs-on: macos-15 | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| COMUX_NOTARY_APPLE_ID: ${{ secrets.APPLE_NOTARY_APPLE_ID }} | |
| COMUX_NOTARY_TEAM_ID: ${{ secrets.APPLE_NOTARY_TEAM_ID }} | |
| COMUX_NOTARY_PASSWORD: ${{ secrets.APPLE_NOTARY_PASSWORD }} | |
| steps: | |
| - name: Poll pending notarizations | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| artifacts_path="$RUNNER_TEMP/comux-notarization-artifacts.tsv" | |
| artifacts_loaded=0 | |
| for attempt in 1 2 3; do | |
| if gh api --paginate \ | |
| "/repos/${GITHUB_REPOSITORY}/actions/artifacts?per_page=100" \ | |
| --jq '.artifacts[] | select(.expired == false and (.name | startswith("comux-notarization-"))) | [.id, .name, .workflow_run.id] | @tsv' \ | |
| > "$artifacts_path"; then | |
| artifacts_loaded=1 | |
| break | |
| fi | |
| echo "::warning::Could not list notarization artifacts (attempt $attempt of 3)." | |
| if [[ "$attempt" -lt 3 ]]; then | |
| sleep "$((attempt * 4))" | |
| fi | |
| done | |
| if [[ "$artifacts_loaded" -ne 1 ]]; then | |
| echo "::warning::GitHub is unavailable; skipping this notarization poll." | |
| exit 0 | |
| fi | |
| if [[ ! -s "$artifacts_path" ]]; then | |
| echo "No pending Comux notarization artifacts found." | |
| exit 0 | |
| fi | |
| failures=0 | |
| dispatched=0 | |
| has_asset() { | |
| local asset_name="$1" | |
| local assets_path="$2" | |
| grep -Fx "$asset_name" "$assets_path" >/dev/null | |
| } | |
| while IFS=$'\t' read -r artifact_id artifact_name artifact_run_id; do | |
| if [[ -z "$artifact_id" || -z "$artifact_name" || -z "$artifact_run_id" ]]; then | |
| continue | |
| fi | |
| artifact_dir="$RUNNER_TEMP/${artifact_name}-${artifact_run_id}" | |
| rm -rf "$artifact_dir" | |
| mkdir -p "$artifact_dir" | |
| echo "Inspecting $artifact_name from run $artifact_run_id." | |
| if ! gh run download "$artifact_run_id" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --name "$artifact_name" \ | |
| --dir "$artifact_dir"; then | |
| echo "::warning::Could not download $artifact_name from run $artifact_run_id." | |
| failures=1 | |
| continue | |
| fi | |
| state_path="$artifact_dir/comux-notarization.txt" | |
| if [[ ! -f "$state_path" ]]; then | |
| echo "::warning::Missing notarization state file in $artifact_name." | |
| failures=1 | |
| continue | |
| fi | |
| version="" | |
| release_repository="$GITHUB_REPOSITORY" | |
| submission_id="" | |
| source_run_id="$artifact_run_id" | |
| while IFS='=' read -r key value; do | |
| case "$key" in | |
| VERSION) | |
| version="$value" | |
| ;; | |
| REPOSITORY) | |
| release_repository="$value" | |
| ;; | |
| SUBMISSION_ID) | |
| submission_id="$value" | |
| ;; | |
| RUN_ID) | |
| source_run_id="$value" | |
| ;; | |
| esac | |
| done < "$state_path" | |
| if [[ -z "$version" || -z "$submission_id" || -z "$source_run_id" ]]; then | |
| echo "::warning::Incomplete notarization state in $artifact_name." | |
| failures=1 | |
| continue | |
| fi | |
| normalized_version="${version#v}" | |
| tag="v${normalized_version}" | |
| archive_name="comux-${normalized_version}.zip" | |
| cask_name="comux.rb" | |
| release_assets_path="$RUNNER_TEMP/${artifact_name}-${artifact_run_id}-release-assets.txt" | |
| if ! gh release view "$tag" \ | |
| --repo "$release_repository" \ | |
| --json assets \ | |
| --jq '.assets[].name' > "$release_assets_path"; then | |
| echo "::warning::Release $tag does not exist in $release_repository. Create the draft release before finalization." | |
| failures=1 | |
| continue | |
| fi | |
| if has_asset "$archive_name" "$release_assets_path" && has_asset "$cask_name" "$release_assets_path"; then | |
| echo "Release $tag is complete; retiring $artifact_name." | |
| if ! gh api \ | |
| --method DELETE \ | |
| "/repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}"; then | |
| echo "::warning::Could not retire completed notarization artifact $artifact_name." | |
| fi | |
| continue | |
| fi | |
| if [[ -z "${COMUX_NOTARY_APPLE_ID:-}" || -z "${COMUX_NOTARY_TEAM_ID:-}" || -z "${COMUX_NOTARY_PASSWORD:-}" ]]; then | |
| echo "::error::Apple notarization credentials are required to poll pending submission $submission_id for $tag." | |
| failures=1 | |
| continue | |
| fi | |
| info_path="$RUNNER_TEMP/${artifact_name}-${artifact_run_id}-notary-info.json" | |
| if ! xcrun notarytool info "$submission_id" \ | |
| --apple-id "$COMUX_NOTARY_APPLE_ID" \ | |
| --team-id "$COMUX_NOTARY_TEAM_ID" \ | |
| --password "$COMUX_NOTARY_PASSWORD" \ | |
| --no-progress \ | |
| --output-format json > "$info_path"; then | |
| echo "::warning::Could not fetch Apple notarization status for $submission_id." | |
| failures=1 | |
| continue | |
| fi | |
| status="$(plutil -extract status raw -o - "$info_path")" | |
| case "$status" in | |
| Accepted) | |
| echo "Notarization accepted for $tag; dispatching release finalization." | |
| gh workflow run release.yml \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --ref "$DEFAULT_BRANCH" \ | |
| -f "version=$normalized_version" \ | |
| -f "repository=$release_repository" \ | |
| -f "notarization_submission_id=$submission_id" \ | |
| -f "notarization_run_id=$source_run_id" | |
| dispatched=$((dispatched + 1)) | |
| ;; | |
| Invalid|Rejected) | |
| log_path="$RUNNER_TEMP/${artifact_name}-${artifact_run_id}-notary-log.json" | |
| xcrun notarytool log "$submission_id" "$log_path" \ | |
| --apple-id "$COMUX_NOTARY_APPLE_ID" \ | |
| --team-id "$COMUX_NOTARY_TEAM_ID" \ | |
| --password "$COMUX_NOTARY_PASSWORD" || true | |
| echo "::error::Notarization $submission_id for $tag finished with status $status. Log: $log_path" | |
| failures=1 | |
| ;; | |
| *) | |
| echo "Notarization $submission_id for $tag is $status; will check again later." | |
| ;; | |
| esac | |
| done < "$artifacts_path" | |
| echo "Dispatched $dispatched release finalization run(s)." | |
| if [[ "$failures" -ne 0 ]]; then | |
| exit 1 | |
| fi |