name: Apple release (macOS DMG) # Builds, signs, notarizes, and publishes the direct-download macOS build: # - a Developer ID–signed, notarized VniDrop-.dmg, # - a Sparkle appcast.xml (both attached to the GitHub Release), and # - an updated Homebrew cask pushed to the sudosylabs/homebrew-vnidrop tap. # # The App Store / TestFlight build is NOT produced here — that goes through Xcode # Organizer / App Store Connect. This workflow only covers direct distribution. # # Trigger: push the canonical vMAJOR.MINOR.PATCH tag (must point at a commit on # master), or run manually using the version committed in version.properties. on: push: tags: - "v*.*.*" workflow_dispatch: permissions: contents: read concurrency: group: apple-release-${{ github.ref }} cancel-in-progress: false defaults: run: shell: bash jobs: build: name: Build & notarize DMG runs-on: macos-latest timeout-minutes: 90 permissions: contents: write outputs: version: ${{ steps.version.outputs.app }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 persist-credentials: false - name: Verify tag is on master if: github.event_name == 'push' run: | if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/master; then echo "Release tags must point to a commit on master" >&2 exit 1 fi - name: Resolve canonical version id: version run: | packaging/version/resolve-version.sh verify >/dev/null version="$(packaging/version/resolve-version.sh product)" echo "app=$version" >> "$GITHUB_OUTPUT" - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode.app - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # v1 with: toolchain: stable targets: aarch64-apple-darwin - name: Cache Cargo uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | ~/.cargo/registry ~/.cargo/git target key: apple-release-cargo-${{ hashFiles('Cargo.lock') }} restore-keys: apple-release-cargo- - name: Install tooling run: brew install xcodegen swiftlint create-dmg - name: Install Bun uses: oven-sh/setup-bun@v2 - name: Download Sparkle tools # generate_appcast + sign_update ship in the Sparkle release tarball. run: | set -euo pipefail ver="2.9.4" curl -fsSL -o /tmp/sparkle.tar.xz \ "https://github.com/sparkle-project/Sparkle/releases/download/${ver}/Sparkle-${ver}.tar.xz" mkdir -p /tmp/sparkle && tar -xJf /tmp/sparkle.tar.xz -C /tmp/sparkle echo "SPARKLE_BIN=/tmp/sparkle/bin" >> "$GITHUB_ENV" - name: Import Developer ID certificate env: CERT_P12_BASE64: ${{ secrets.DEVELOPER_ID_CERT_P12 }} CERT_PASSWORD: ${{ secrets.DEVELOPER_ID_CERT_PASSWORD }} run: | set -euo pipefail keychain="$RUNNER_TEMP/signing.keychain-db" kpw="$(openssl rand -hex 20)" security create-keychain -p "$kpw" "$keychain" security set-keychain-settings -lut 21600 "$keychain" security unlock-keychain -p "$kpw" "$keychain" echo "$CERT_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12" security import "$RUNNER_TEMP/cert.p12" -k "$keychain" -P "$CERT_PASSWORD" \ -T /usr/bin/codesign -T /usr/bin/security security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$kpw" "$keychain" # Prepend our keychain so codesign/xcodebuild can find the identity. security list-keychains -d user -s "$keychain" $(security list-keychains -d user | tr -d '"') rm -f "$RUNNER_TEMP/cert.p12" - name: Store notarytool credentials env: NOTARY_KEY_P8: ${{ secrets.NOTARY_API_KEY }} NOTARY_KEY_ID: ${{ secrets.NOTARY_KEY_ID }} NOTARY_ISSUER: ${{ secrets.NOTARY_ISSUER }} run: | set -euo pipefail echo "$NOTARY_KEY_P8" | base64 --decode > "$RUNNER_TEMP/notary.p8" xcrun notarytool store-credentials vnidrop-notary \ --key "$RUNNER_TEMP/notary.p8" \ --key-id "$NOTARY_KEY_ID" \ --issuer "$NOTARY_ISSUER" echo "NOTARY_PROFILE=vnidrop-notary" >> "$GITHUB_ENV" - name: Write Sparkle signing key env: SPARKLE_ED_PRIVATE_KEY: ${{ secrets.SPARKLE_ED_PRIVATE_KEY }} run: | printf '%s' "$SPARKLE_ED_PRIVATE_KEY" > "$RUNNER_TEMP/sparkle_ed_private_key" echo "SPARKLE_ED_KEY_FILE=$RUNNER_TEMP/sparkle_ed_private_key" >> "$GITHUB_ENV" - name: Build, sign & notarize DMG run: apple/scripts/build-dmg.sh - name: Generate appcast env: RELEASE_REPO: ${{ github.repository }} run: apple/scripts/generate-appcast.sh - name: Upload artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: vnidrop-${{ steps.version.outputs.app }}-macos-dmg path: | apple/dist/VniDrop-*.dmg apple/dist/appcast.xml if-no-files-found: error retention-days: 14 - name: Publish GitHub Release if: github.event_name == 'push' && github.ref_type == 'tag' env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | set -euo pipefail tag="$GITHUB_REF_NAME" version="${tag#v}" if gh release view "$tag" >/dev/null 2>&1; then echo "Release $tag already exists; refusing to replace assets" >&2 exit 1 fi gh release create "$tag" \ "apple/dist/VniDrop-${version}.dmg" \ "apple/dist/appcast.xml" \ --verify-tag \ --title "VniDrop $version" \ --generate-notes update-cask: name: Update Homebrew cask needs: build if: github.event_name == 'push' && github.ref_type == 'tag' runs-on: ubuntu-22.04 timeout-minutes: 15 steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Download DMG artifact uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 with: name: vnidrop-${{ needs.build.outputs.version }}-macos-dmg path: dist - name: Render cask env: VERSION: ${{ needs.build.outputs.version }} run: | set -euo pipefail sha="$(sha256sum "dist/VniDrop-${VERSION}.dmg" | cut -d' ' -f1)" sed -e "s/^ version \".*\"/ version \"${VERSION}\"/" \ -e "s/^ sha256 \".*\"/ sha256 \"${sha}\"/" \ packaging/homebrew/vnidrop.rb > /tmp/vnidrop.rb echo "Rendered cask:"; cat /tmp/vnidrop.rb - name: Push to tap env: TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} VERSION: ${{ needs.build.outputs.version }} run: | set -euo pipefail git clone "https://x-access-token:${TAP_TOKEN}@github.com/sudosylabs/homebrew-vnidrop.git" tap mkdir -p tap/Casks cp /tmp/vnidrop.rb tap/Casks/vnidrop.rb cd tap git config user.name "vnidrop-release-bot" git config user.email "release-bot@users.noreply.github.com" git add Casks/vnidrop.rb git commit -m "vnidrop ${VERSION}" || { echo "no cask changes"; exit 0; } git push