# This is a **reuseable** workflow that bundles the Desktop App for macOS. # It doesn't get triggered on its own. It gets used in multiple workflows: # - release.yml # - canary.yml # - pr-comment-bundle-desktop.yml on: workflow_call: inputs: version: description: 'Version to set for the build' required: false default: "" type: string signing: description: 'Whether to perform signing and notarization' required: false default: false type: boolean quick_test: description: 'Whether to perform the quick launch test' required: false default: true type: boolean ref: description: 'Git ref to checkout (branch, tag, or SHA). Defaults to main branch if not specified.' required: false type: string default: '' secrets: OSX_CODESIGN_ROLE: required: false name: Reusable workflow to bundle desktop app jobs: bundle-desktop: runs-on: macos-latest name: Bundle Desktop App on macOS permissions: id-token: write contents: read outputs: artifact-url: ${{ steps.upload-app-bundle.outputs.artifact-url }} steps: # Debug information about the workflow and inputs - name: Debug workflow info env: WORKFLOW_NAME: ${{ github.workflow }} WORKFLOW_REF: ${{ github.ref }} EVENT_NAME: ${{ github.event_name }} REPOSITORY: ${{ github.repository }} INPUT_REF: ${{ inputs.ref }} INPUT_VERSION: ${{ inputs.version }} INPUT_SIGNING: ${{ inputs.signing }} INPUT_QUICK_TEST: ${{ inputs.quick_test }} run: | echo "=== Workflow Information ===" echo "Workflow: ${WORKFLOW_NAME}" echo "Ref: ${WORKFLOW_REF}" echo "Event: ${EVENT_NAME}" echo "Repo: ${REPOSITORY}" echo "" echo "=== Input Parameters ===" echo "Build ref: ${INPUT_REF:-}" echo "Version: ${INPUT_VERSION:-not set}" echo "Signing: ${INPUT_SIGNING:-false}" echo "Quick test: ${INPUT_QUICK_TEST:-true}" # Check initial disk space - name: Check initial disk space run: df -h - name: Checkout code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 with: # Only pass ref if it's explicitly set, otherwise let checkout action use its default behavior ref: ${{ inputs.ref != '' && inputs.ref || '' }} fetch-depth: 0 - name: Debug git status run: | echo "=== Git Status ===" git status echo "" echo "=== Current Commit ===" git rev-parse HEAD git rev-parse --abbrev-ref HEAD echo "" echo "=== Recent Commits ===" git log --oneline -n 5 echo "" echo "=== Remote Branches ===" git branch -r # Update versions before build - name: Update versions if: ${{ inputs.version != '' }} env: VERSION: ${{ inputs.version }} run: | # Update version in Cargo.toml sed -i.bak "s/^version = \".*\"/version = \"${VERSION}\"/" Cargo.toml rm -f Cargo.toml.bak source ./bin/activate-hermit # Update version in package.json cd ui/desktop npm version "${VERSION}" --no-git-tag-version --allow-same-version - name: Cache Cargo registry uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # pin@v3 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-registry- - name: Cache Cargo index uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # pin@v3 with: path: ~/.cargo/index key: ${{ runner.os }}-cargo-index restore-keys: | ${{ runner.os }}-cargo-index - name: Cache Cargo build uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # pin@v3 with: path: target key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-build- # Install Go for building temporal-service - name: Set up Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # pin@v5 with: go-version: '1.21' # Build the project - name: Build goosed run: source ./bin/activate-hermit && cargo build --release -p goose-server # Build temporal-service using build.sh script - name: Build temporal-service run: | echo "Building temporal-service using build.sh script..." cd temporal-service ./build.sh echo "temporal-service built successfully" # Install and prepare temporal CLI - name: Install temporal CLI via hermit run: | echo "Installing temporal CLI via hermit..." ./bin/hermit install temporal-cli echo "temporal CLI installed successfully" # Post-build cleanup to free space - name: Post-build cleanup run: | echo "Performing post-build cleanup..." # Remove debug artifacts rm -rf target/debug || true # Keep only what's needed for the next steps rm -rf target/release/deps || true rm -rf target/release/build || true rm -rf target/release/incremental || true # Check disk space after cleanup df -h - name: Copy binaries into Electron folder run: | cp target/release/goosed ui/desktop/src/bin/goosed cp temporal-service/temporal-service ui/desktop/src/bin/temporal-service cp bin/temporal ui/desktop/src/bin/temporal - name: Cache npm dependencies uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f # pin@v3 with: path: | ui/desktop/node_modules .hermit/node/cache key: macos-npm-cache-v1-${{ runner.os }}-${{ hashFiles('ui/desktop/package-lock.json') }} restore-keys: | macos-npm-cache-v1-${{ runner.os }}- - name: Install dependencies run: source ../../bin/activate-hermit && npm ci working-directory: ui/desktop # Check disk space before bundling - name: Check disk space before bundling run: df -h - name: Build App run: | source ../../bin/activate-hermit attempt=0 max_attempts=2 until [ $attempt -ge $max_attempts ]; do npm run bundle:default && break attempt=$((attempt + 1)) echo "Attempt $attempt failed. Retrying..." sleep 5 done if [ $attempt -ge $max_attempts ]; then echo "Action failed after $max_attempts attempts." exit 1 fi working-directory: ui/desktop - name: Configure AWS credentials if: ${{ inputs.signing }} uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4 with: role-to-assume: "${{ secrets.OSX_CODESIGN_ROLE }}" aws-region: us-west-2 - name: Codesigning and Notarization if: ${{ inputs.signing }} run: | set -e echo "⬆️ uploading unsigned app" source_job_url="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" unsigned_url="s3://block-goose-artifacts-bucket-production/unsigned/goose-${GITHUB_SHA}-${{ github.run_id }}-arm64.zip" zip -q -u -r out/Goose-darwin-arm64/Goose.zip entitlements.plist # upload unsigned goose to transfer bucket so it can be passed to lambda aws s3 cp --quiet out/Goose-darwin-arm64/Goose.zip "${unsigned_url}" # begin signing echo "🚀 launching signing process" aws lambda invoke \ --function-name codesign_helper \ --cli-binary-format raw-in-base64-out \ --payload "{\"source_s3_url\": \"${unsigned_url}\", \"source_job_url\": \"${source_job_url}\"}" \ response.json > /dev/null if [ "$(jq -r .statusCode response.json)" != "200" ]; then echo "⚠️ lambda function did not return expected status code" exit 1 fi build_number="$(jq -r .body.build_number response.json)" start_time=$(date +%s) while sleep 30; do aws lambda invoke \ --function-name codesign_helper \ --cli-binary-format raw-in-base64-out \ --payload "{\"source_s3_url\": \"${unsigned_url}\", \"build_number\": \"${build_number}\"}" \ response.json > /dev/null if [ "$(jq -r .statusCode response.json)" != "200" ]; then echo "⚠️ signing request returned unexpected response code $(jq -r .statusCode response.json):" jq . response.json exit 1 fi state="$(jq -r .body.state response.json)" if [ "${state}" == "completed" ]; then echo "✅ signing complete ($(($(date +%s) - start_time))s)" break fi if [ $(($(date +%s) - start_time)) -ge 3600 ]; then echo "⚠️ timed out ($(($(date +%s) - start_time))s)" exit 1 fi echo "⏲️ waiting for signing to complete (${state}: $(($(date +%s) - start_time))s)" done # parse lambda response signed_url=$(jq -r .body.destination_url response.json) # download the signed app from S3 echo "⬇️ downloading signed app" aws s3 cp --quiet "${signed_url}" out/Goose-darwin-arm64/Goose.zip working-directory: ui/desktop - name: Final cleanup before artifact upload run: | echo "Performing final cleanup..." # Remove build artifacts that are no longer needed rm -rf target || true # Check disk space after cleanup df -h - name: Upload Desktop artifact id: upload-app-bundle uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # pin@v4 with: name: Goose-darwin-arm64 path: ui/desktop/out/Goose-darwin-arm64/Goose.zip - name: Quick launch test (macOS) if: ${{ inputs.quick_test }} run: | # Ensure no quarantine attributes (if needed) xattr -cr "ui/desktop/out/Goose-darwin-arm64/Goose.app" echo "Opening Goose.app..." open -g "ui/desktop/out/Goose-darwin-arm64/Goose.app" # Give the app a few seconds to start and write logs sleep 5 # Check if it's running if pgrep -f "Goose.app/Contents/MacOS/Goose" > /dev/null; then echo "App appears to be running." else echo "App did not stay open. Possible crash or startup error." exit 1 fi # Kill the app to clean up pkill -f "Goose.app/Contents/MacOS/Goose"