From 128d9b174f3e375d3997c82ed0c681c597b484eb Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 15 Mar 2026 17:39:35 +0800 Subject: [PATCH] feat(workflow): enhance macOS release process with improved build steps and version handling - Updated the macOS release workflow to include a version input, allowing for dynamic version specification. - Refactored build steps to streamline the setup of Node.js, pnpm, and Go tools, improving build efficiency. - Added multiple repository checkouts for dependencies, ensuring all necessary components are available for the build. - Implemented certificate management for code signing, enhancing the security of the release process. - Improved artifact creation and signing steps, ensuring a more robust and reliable release pipeline. Made-with: Cursor --- .github/workflows/notarize-macos.yml | 88 ++++++++ .github/workflows/release-linux.yml | 219 ++++++++++++++++++++ .github/workflows/release-macos.yml | 291 ++++++++++++++++++++++----- 3 files changed, 546 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/notarize-macos.yml create mode 100644 .github/workflows/release-linux.yml diff --git a/.github/workflows/notarize-macos.yml b/.github/workflows/notarize-macos.yml new file mode 100644 index 00000000..20b9dd13 --- /dev/null +++ b/.github/workflows/notarize-macos.yml @@ -0,0 +1,88 @@ +name: Notarize macOS + +on: + workflow_dispatch: + inputs: + run_id: + description: "Release macOS workflow run ID (to download artifacts from)" + required: true + version: + description: "Version used in the release build (e.g. 1.0.0 or 1.0.0-alpha)" + required: true + +permissions: + contents: write + +jobs: + # =================================================================== + # Notarize Yao binaries (arm64 + amd64) + # =================================================================== + notarize: + runs-on: macos-latest + strategy: + matrix: + arch: [arm64, amd64] + steps: + - name: Download Yao Binary + uses: actions/download-artifact@v4 + with: + name: yao-darwin-${{ matrix.arch }} + path: bin + run-id: ${{ github.event.inputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Certificates + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + mkdir -p certs + echo "${{ secrets.APPLE_DEVELOPERIDG2CA }}" | base64 --decode > certs/DeveloperIDG2CA.cer + echo "${{ secrets.APPLE_DISTRIBUTION }}" | base64 --decode > certs/distribution.cer + echo "${{ secrets.APPLE_PRIVATE_KEY }}" | base64 --decode > certs/private_key.p12 + security verify-cert -c certs/DeveloperIDG2CA.cer + security verify-cert -c certs/distribution.cer + + - name: Import Certificates + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security import ./certs/DeveloperIDG2CA.cer -k $KEYCHAIN_PATH -T /usr/bin/codesign + security import ./certs/distribution.cer -k $KEYCHAIN_PATH -T /usr/bin/codesign + security import ./certs/private_key.p12 -k $KEYCHAIN_PATH -P "${{ secrets.APPLE_PRIVATE_KEY_PASSWORD }}" -T /usr/bin/codesign + security list-keychain -d user -s $KEYCHAIN_PATH + + - name: Verify Signature + run: codesign --verify --deep --strict --verbose=2 bin/yao + + - name: Notarize Yao ${{ matrix.arch }} + timeout-minutes: 15 + env: + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_TEAME_ID: ${{ secrets.APPLE_TEAME_ID }} + APPLE_APP_SPEC_PASS: ${{ secrets.APPLE_APP_SPEC_PASS }} + run: | + zip -j bin/yao.zip bin/yao + + SUBMIT_OUT=$(xcrun notarytool submit bin/yao.zip \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAME_ID" \ + --password "$APPLE_APP_SPEC_PASS" \ + --wait --timeout 10m --output-format json 2>&1) || true + echo "$SUBMIT_OUT" + + STATUS=$(echo "$SUBMIT_OUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('status',''))" 2>/dev/null || true) + SUB_ID=$(echo "$SUBMIT_OUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) + + if [ "$STATUS" != "Accepted" ]; then + echo "::error::Yao ${{ matrix.arch }} notarization failed (status: $STATUS)" + [ -n "$SUB_ID" ] && xcrun notarytool log "$SUB_ID" \ + --apple-id "$APPLE_ID" \ + --team-id "$APPLE_TEAME_ID" \ + --password "$APPLE_APP_SPEC_PASS" || true + exit 1 + fi + echo "Yao ${{ matrix.arch }} notarization accepted." diff --git a/.github/workflows/release-linux.yml b/.github/workflows/release-linux.yml new file mode 100644 index 00000000..aa6f572a --- /dev/null +++ b/.github/workflows/release-linux.yml @@ -0,0 +1,219 @@ +name: Release Linux + +on: + workflow_dispatch: + inputs: + version: + description: "Release version (e.g. 1.0.0 or 1.0.0-alpha). Leave empty to read from share/const.go." + required: false + push: + tags: + - "v*" + +permissions: + contents: write + +env: + IMAGE_NAME: yaoapp/yao + +jobs: + # =================================================================== + # Build Linux Binaries (amd64 + arm64) + # Uses the yaoapp/yao-build container which has all cross-compile deps. + # =================================================================== + build-linux: + runs-on: ubuntu-latest + container: + image: yaoapp/yao-build:1.0.0 + env: + CF_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }} + CF_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }} + R2_BUCKET: ${{ secrets.R2_BUCKET }} + R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} + steps: + - name: Configure R2 For Cloudflare + run: | + aws configure set aws_access_key_id $CF_ACCESS_KEY_ID + aws configure set aws_secret_access_key $CF_SECRET_ACCESS_KEY + aws configure set default.region us-east-1 + aws configure set default.s3.signature_version s3v4 + aws configure set default.s3.endpoint_url https://$R2_ACCOUNT_ID.r2.cloudflarestorage.com + + - name: Build + run: | + export PATH=$PATH:/github/home/go/bin + /app/build.sh + ls -l /data + + - name: Get Version + id: version + run: | + if [ -n "${{ github.event.inputs.version }}" ]; then + echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + else + VERSION=$(cat share/const.go | grep 'const VERSION' | awk '{print $4}' | sed 's/"//g') + echo "version=${VERSION}" >> $GITHUB_OUTPUT + fi + + - name: Push To R2 + run: | + for file in /data/*; do + aws s3 cp "$file" s3://$R2_BUCKET/archives/ \ + --endpoint-url https://$R2_ACCOUNT_ID.r2.cloudflarestorage.com + done + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: yao-linux + path: /data/* + + # =================================================================== + # GitHub Release + Tag + # =================================================================== + release: + needs: build-linux + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Get Version + id: version + run: | + if [ -n "${{ github.event.inputs.version }}" ]; then + echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + else + VERSION=$(cat share/const.go | grep 'const VERSION' | awk '{print $4}' | sed 's/"//g') + echo "version=${VERSION}" >> $GITHUB_OUTPUT + fi + + - name: Download Linux Artifacts + uses: actions/download-artifact@v4 + with: + name: yao-linux + path: dist + + - name: List Artifacts + run: ls -lh dist/ + + - name: Create Tag (if manual dispatch) + if: github.event_name == 'workflow_dispatch' + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}" || true + git push origin "v${{ steps.version.outputs.version }}" || true + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.version }} + name: Yao v${{ steps.version.outputs.version }} + files: dist/* + generate_release_notes: true + + # =================================================================== + # Docker Images (multi-platform linux/amd64 + linux/arm64) + # Builds after R2 upload so Dockerfiles can curl binaries from R2. + # =================================================================== + docker: + needs: build-linux + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Get Version + id: version + run: | + if [ -n "${{ github.event.inputs.version }}" ]; then + echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + else + VERSION=$(cat share/const.go | grep 'const VERSION' | awk '{print $4}' | sed 's/"//g') + echo "version=${VERSION}" >> $GITHUB_OUTPUT + fi + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build & Push Development (amd64) + uses: docker/build-push-action@v6 + with: + context: ./docker/development + platforms: linux/amd64 + build-args: | + VERSION=${{ steps.version.outputs.version }}-unstable + ARCH=amd64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-amd64-dev + + - name: Build & Push Development (arm64) + uses: docker/build-push-action@v6 + with: + context: ./docker/development + platforms: linux/arm64 + build-args: | + VERSION=${{ steps.version.outputs.version }}-unstable + ARCH=arm64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-arm64-dev + + - name: Build & Push Production (amd64) + uses: docker/build-push-action@v6 + with: + context: ./docker/production + platforms: linux/amd64 + build-args: | + VERSION=${{ steps.version.outputs.version }}-unstable + ARCH=amd64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-amd64 + + - name: Build & Push Production (arm64) + uses: docker/build-push-action@v6 + with: + context: ./docker/production + platforms: linux/arm64 + build-args: | + VERSION=${{ steps.version.outputs.version }}-unstable + ARCH=arm64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-arm64 + + - name: Build & Push Slim (amd64) + uses: docker/build-push-action@v6 + with: + context: ./docker/production-slim + platforms: linux/amd64 + build-args: | + VERSION=${{ steps.version.outputs.version }}-unstable + ARCH=amd64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-amd64-slim + + - name: Build & Push Slim (arm64) + uses: docker/build-push-action@v6 + with: + context: ./docker/production-slim + platforms: linux/arm64 + build-args: | + VERSION=${{ steps.version.outputs.version }}-unstable + ARCH=arm64 + push: true + tags: ${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-arm64-slim diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 43d2353a..2229bc62 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -1,64 +1,251 @@ -name: Release MacOS Artifacts +name: Release macOS on: workflow_dispatch: + inputs: + version: + description: "Release version (e.g. 1.0.0 or 1.0.0-alpha). Leave empty to read from share/const.go." + required: false + +permissions: + contents: write jobs: - release: - runs-on: "macos-12" - timeout-minutes: 120 - + # =================================================================== + # Build Yao macOS binaries (arm64 + amd64) — one job, both arches + # =================================================================== + build: + runs-on: macos-latest steps: - - name: Download latest artifacts + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: Install pnpm + run: npm install -g pnpm + + - name: Setup Cache + uses: actions/cache@v4 + with: + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Checkout Kun + uses: actions/checkout@v4 + with: + repository: yaoapp/kun + path: kun + + - name: Checkout Xun + uses: actions/checkout@v4 + with: + repository: yaoapp/xun + path: xun + + - name: Checkout Gou + uses: actions/checkout@v4 + with: + repository: yaoapp/gou + path: gou + + - name: Checkout V8Go + uses: actions/checkout@v4 + with: + repository: yaoapp/v8go + path: v8go + + - name: Unzip libv8 run: | - echo "Downloading latest artifacts..." - ARTIFACT_URL="https://api.github.com/repos/YaoApp/yao/actions/artifacts" - ARTIFACT_CONTENT=$(curl -s -H "Accept: application/vnd.github.v3+json" $ARTIFACT_URL) - echo $ARTIFACT_CONTENT - ARTIFACTS=$(echo $ARTIFACT_CONTENT | jq -r '.artifacts[] | select(.name | contains("yao-macos")) | .id') - for id in $ARTIFACTS; do - echo "https://api.github.com/repos/YaoApp/yao/actions/artifacts/$id/zip" - curl -L -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/YaoApp/yao/actions/artifacts/$id/zip" \ - -o artifact.zip - unzip artifact.zip -d ./artifacts - rm artifact.zip - break + files=$(find ./v8go -name "libv8*.zip") + for file in $files; do + dir=$(dirname "$file") + echo "Extracting $file to directory $dir" + unzip -o -d $dir $file + rm -rf $dir/__MACOSX done - ls -l ./artifacts - # - name: Submit notarization request - # run: | - # echo "Submitting notarization request..." - # UUID=$(xcrun altool --notarize-app --primary-bundle-id "com.example.yourapp" \ - # --username "your-apple-id" --password "app-specific-password" \ - # --file ./artifacts/your-binary-file) + - name: Checkout CUI v1.0 + uses: actions/checkout@v4 + with: + repository: yaoapp/cui + path: cui-v1.0 - # echo "Notarization UUID: $UUID" - # echo "$UUID" > notarization_uuid.txt + - name: Checkout Yao-Init + uses: actions/checkout@v4 + with: + repository: yaoapp/yao-init + path: yao-init - # - name: Check notarization status - # id: check_notarization - # timeout-minutes: 120 - # run: | - # UUID=$(cat notarization_uuid.txt) - # STATUS="in progress" - # while [[ "$STATUS" == "in progress" ]]; do - # STATUS=$(xcrun altool --notarization-info "$UUID" \ - # --username "your-apple-id" --password "app-specific-password") - # echo "Notarization status: $STATUS" - # if [[ "$STATUS" == *"success"* ]]; then - # echo "::set-output name=status::success" - # break - # elif [[ "$STATUS" == *"invalid"* ]]; then - # echo "::set-output name=status::failed" - # break - # fi - # done + - name: Move Dependencies + run: | + mv kun ../ + mv xun ../ + mv gou ../ + mv v8go ../ + mv cui-v1.0 ../ + mv yao-init ../ + rm -f ../cui-v1.0/packages/setup/vite.config.ts.* - # - name: Create Release - # if: steps.check_notarization.outputs.status == 'success' - # run: | - # echo "Creating a release..." - # VERSION=$(git rev-parse --short HEAD) - # gh release create "v1.0.0-$VERSION" ./artifacts/* --title "Release v0.10.4-$VERSION" --notes "Notarization succeeded. This is the release for version v1.0.0-$VERSION." + - name: Checkout Yao + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + + - name: Setup Go Tools + run: make tools + + - name: Get Version + id: version + run: | + if [ -n "${{ github.event.inputs.version }}" ]; then + echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT + else + VERSION=$(cat share/const.go | grep 'const VERSION' | awk '{print $4}' | sed 's/"//g') + echo "version=${VERSION}" >> $GITHUB_OUTPUT + fi + + - name: Make Artifacts macOS + run: make artifacts-macos + env: + VERSION: ${{ steps.version.outputs.version }} + + - name: List Build Output + run: ls -lh dist/release/ + + - name: Install Certificates + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + mkdir -p certs + echo "${{ secrets.APPLE_DEVELOPERIDG2CA }}" | base64 --decode > certs/DeveloperIDG2CA.cer + echo "${{ secrets.APPLE_DISTRIBUTION }}" | base64 --decode > certs/distribution.cer + echo "${{ secrets.APPLE_PRIVATE_KEY }}" | base64 --decode > certs/private_key.p12 + security verify-cert -c certs/DeveloperIDG2CA.cer + security verify-cert -c certs/distribution.cer + + - name: Import Certificates + env: + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security import ./certs/DeveloperIDG2CA.cer -k $KEYCHAIN_PATH -T /usr/bin/codesign + security import ./certs/distribution.cer -k $KEYCHAIN_PATH -T /usr/bin/codesign + security import ./certs/private_key.p12 -k $KEYCHAIN_PATH -P "${{ secrets.APPLE_PRIVATE_KEY_PASSWORD }}" -T /usr/bin/codesign + security list-keychain -d user -s $KEYCHAIN_PATH + + - name: Sign Yao Binaries + run: | + VERSION="${{ steps.version.outputs.version }}" + IDENTITY="Developer ID Application: ${{ secrets.APPLE_SIGN }}" + for ARCH in arm64 amd64; do + BIN="dist/release/yao-${VERSION}-unstable-darwin-${ARCH}" + codesign --force --verbose --timestamp --options runtime --sign "$IDENTITY" "$BIN" + codesign --verify --deep --strict --verbose=2 "$BIN" + done + + - name: Prepare Output and Checksums + id: output + run: | + VERSION="${{ steps.version.outputs.version }}" + for ARCH in arm64 amd64; do + mkdir -p /tmp/yao-output-${ARCH} + cp "dist/release/yao-${VERSION}-unstable-darwin-${ARCH}" "/tmp/yao-output-${ARCH}/yao" + chmod +x "/tmp/yao-output-${ARCH}/yao" + done + + mkdir -p /tmp/checksums + shasum -a 256 /tmp/yao-output-arm64/yao | awk '{print $1" yao"}' > /tmp/checksums/yao-darwin-arm64.sha256 + shasum -a 256 /tmp/yao-output-amd64/yao | awk '{print $1" yao"}' > /tmp/checksums/yao-darwin-amd64.sha256 + echo "=== Checksums ===" + cat /tmp/checksums/*.sha256 + + - name: Upload arm64 Binary + uses: actions/upload-artifact@v4 + with: + name: yao-darwin-arm64 + path: /tmp/yao-output-arm64/yao + + - name: Upload amd64 Binary + uses: actions/upload-artifact@v4 + with: + name: yao-darwin-amd64 + path: /tmp/yao-output-amd64/yao + + - name: Upload arm64 Checksum + uses: actions/upload-artifact@v4 + with: + name: yao-darwin-arm64-sha256 + path: /tmp/checksums/yao-darwin-arm64.sha256 + + - name: Upload amd64 Checksum + uses: actions/upload-artifact@v4 + with: + name: yao-darwin-amd64-sha256 + path: /tmp/checksums/yao-darwin-amd64.sha256 + + # =================================================================== + # GitHub Release (if version provided) + # =================================================================== + release: + needs: build + runs-on: ubuntu-latest + if: github.event.inputs.version != '' + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Download arm64 Artifact + uses: actions/download-artifact@v4 + with: + name: yao-darwin-arm64 + path: dist/arm64 + + - name: Download amd64 Artifact + uses: actions/download-artifact@v4 + with: + name: yao-darwin-amd64 + path: dist/amd64 + + - name: Download Checksums + uses: actions/download-artifact@v4 + with: + pattern: yao-darwin-*-sha256 + path: dist/checksums + merge-multiple: true + + - name: Prepare Release Files + run: | + VERSION="${{ github.event.inputs.version }}" + mkdir -p release + cp dist/arm64/yao "release/yao-${VERSION}-darwin-arm64" + cp dist/amd64/yao "release/yao-${VERSION}-darwin-amd64" + cp dist/checksums/*.sha256 release/ + chmod +x release/yao-* + ls -la release/ + + - name: Create Tag + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git tag -a "v${{ github.event.inputs.version }}-macos" -m "Release v${{ github.event.inputs.version }} (macOS)" || true + git push origin "v${{ github.event.inputs.version }}-macos" || true + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ github.event.inputs.version }}-macos + name: Yao v${{ github.event.inputs.version }} (macOS) + files: release/* + generate_release_notes: true + draft: true