# This workflow is triggered by a comment on PR with the text ".bundle" # It bundles the ARM64 Desktop App, then creates a PR comment with a link to download the app. # # SECURITY: This workflow checks out and builds code from PRs. To prevent # malicious code execution (GHSA-4h72-4h3w-4587), we verify the commenter # has write access before proceeding. on: issue_comment: types: [created] workflow_dispatch: inputs: pr_number: description: 'PR number to comment on' required: true type: string # permissions needed for reacting to IssueOps commands on PRs permissions: pull-requests: write checks: read name: Bundle ARM64 Desktop App concurrency: group: ${{ github.workflow }}-${{ (github.event.issue && github.event.issue.number) || github.event.inputs.pr_number }} cancel-in-progress: true jobs: trigger-on-command: if: > github.event_name == 'workflow_dispatch' || (github.event.issue.pull_request && contains(github.event.comment.body, '.bundle')) name: Trigger on ".bundle" PR comment runs-on: ubuntu-latest outputs: continue: ${{ steps.security_check.outputs.authorized }} pr_number: ${{ steps.command.outputs.issue_number || github.event.inputs.pr_number }} pr_sha: ${{ steps.get_pr_info.outputs.sha }} steps: # SECURITY: Verify commenter has write access BEFORE any checkout # This prevents attackers from triggering builds on their own malicious PRs - name: Verify commenter permissions id: security_check uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | // workflow_dispatch requires repo write access, so it's inherently safe if (context.eventName === 'workflow_dispatch') { core.setOutput('authorized', 'true'); console.log('✅ workflow_dispatch - authorized'); return; } const commenter = context.payload.comment.user.login; console.log(`Checking permissions for: ${commenter}`); try { const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: commenter }); const allowed = ['admin', 'maintain', 'write'].includes(permission.permission); console.log(`Permission level: ${permission.permission}, Authorized: ${allowed}`); if (!allowed) { // Post a comment explaining the rejection await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, body: `⚠️ @${commenter} Only repository collaborators with write access can trigger builds.` }); core.setOutput('authorized', 'false'); } else { core.setOutput('authorized', 'true'); } } catch (error) { console.log(`Permission check failed: ${error.message}`); core.setOutput('authorized', 'false'); } - name: Debug workflow trigger if: steps.security_check.outputs.authorized == 'true' env: WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_REF: ${{ github.ref }} EVENT_NAME: ${{ github.event_name }} EVENT_ACTION: ${{ github.event.action }} ACTOR: ${{ github.actor }} REPOSITORY: ${{ github.repository }} run: | echo "=== Workflow Trigger Info ===" echo "Workflow: ${WORKFLOW_NAME}" echo "Ref: ${WORKFLOW_REF}" echo "Event: ${EVENT_NAME}" echo "Action: ${EVENT_ACTION}" echo "Actor: ${ACTOR}" echo "Repository: ${REPOSITORY}" - name: Run command action if: steps.security_check.outputs.authorized == 'true' uses: github/command@3442f3fa1efe01bdb024b157083c337902d17372 # v2.0.3 id: command with: command: ".bundle" skip_reviews: true reaction: "eyes" allowed_contexts: pull_request # Get the PR's SHA - name: Get PR info id: get_pr_info if: steps.security_check.outputs.authorized == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | let prNumber; if (context.eventName === 'workflow_dispatch') { prNumber = context.payload.inputs.pr_number; } else { prNumber = context.payload.issue.number; } if (!prNumber) { throw new Error('No PR number found'); } console.log('Using PR number:', prNumber); const { data: pr } = await github.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, pull_number: parseInt(prNumber, 10) }); console.log('PR Details:', { number: pr.number, head: { ref: pr.head.ref, sha: pr.head.sha, label: pr.head.label }, base: { ref: pr.base.ref, sha: pr.base.sha, label: pr.base.label } }); core.setOutput('sha', pr.head.sha); bundle-desktop: needs: [trigger-on-command] if: ${{ needs.trigger-on-command.outputs.continue == 'true' }} uses: ./.github/workflows/bundle-desktop.yml permissions: id-token: write contents: read with: signing: false ref: ${{ needs.trigger-on-command.outputs.pr_sha }} pr-comment-arm64: name: PR Comment with macOS ARM64 App runs-on: ubuntu-latest needs: [trigger-on-command, bundle-desktop] permissions: pull-requests: write steps: - name: Download ARM64 artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: Goose-darwin-arm64 path: arm64-dist - name: Comment on PR with ARM64 download link uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 with: issue-number: ${{ needs.trigger-on-command.outputs.pr_number }} body: | ### macOS ARM64 Desktop App (Apple Silicon) [📱 Download macOS Desktop App (arm64, unsigned)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/Goose-darwin-arm64.zip) **Instructions:** The easiest way is to just run the following script: `./scripts/pre-release.sh` script which will download the latest release (or you can specify the release you need), does the unzip, xattr to get it out of quarantine and signs it. If you need to do this manually: * Download the file * Unzip * run `xattr -r -d com.apple.quarantine '/path/to/Goose.app'` * optionally run `codesign --force --deep --sign - --entitlements ui/desktop/entitlements.plist '/path/to/Goose.app'` * start the app The signing step is only needed if you do something that uses mac entitlements like speech to text