Make Notarized DMG Release #1042
This file contains 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: Make Notarized DMG Release | |
on: | |
workflow_dispatch: | |
inputs: | |
release-type: | |
description: "Build type (product review or public release)" | |
required: true | |
default: review | |
type: choice | |
options: | |
- review | |
- release | |
create-dmg: | |
description: "Create DMG image" | |
required: true | |
default: false | |
type: boolean | |
asana-task-url: | |
description: "Asana release task URL" | |
required: false | |
type: string | |
workflow_call: | |
inputs: | |
release-type: | |
description: "Build type (product review or public release)" | |
required: true | |
default: release | |
type: string | |
create-dmg: | |
description: "Create DMG image" | |
required: true | |
default: true | |
type: boolean | |
asana-task-url: | |
description: "Asana release task URL" | |
required: false | |
type: string | |
branch: | |
description: "Branch name" | |
required: false | |
type: string | |
secrets: | |
BUILD_CERTIFICATE_BASE64: | |
required: true | |
P12_PASSWORD: | |
required: true | |
KEYCHAIN_PASSWORD: | |
required: true | |
APPSTORE_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
CI_PROVISION_PROFILE_BASE64: | |
required: true | |
DBP_AGENT_APPSTORE_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
DBP_AGENT_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
INTEGRATION_TESTS_APPSTORE_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
INTEGRATION_TESTS_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_NOTIFICATIONS_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
UNIT_TESTS_APPSTORE_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
UNIT_TESTS_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
VPN_APPEX_APPSTORE_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
VPN_APP_APPSTORE_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
VPN_APP_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
VPN_PROXY_EXTENSION_CI_PROVISION_PROFILE_BASE64: | |
required: true | |
APPLE_API_KEY_BASE64: | |
required: true | |
APPLE_API_KEY_ID: | |
required: true | |
APPLE_API_KEY_ISSUER: | |
required: true | |
ASANA_ACCESS_TOKEN: | |
required: true | |
MM_HANDLES_BASE64: | |
required: true | |
MM_WEBHOOK_URL: | |
required: true | |
AWS_ACCESS_KEY_ID: | |
required: true | |
AWS_ACCESS_KEY_ID_RELEASE_S3: | |
required: true | |
AWS_SECRET_ACCESS_KEY: | |
required: true | |
AWS_SECRET_ACCESS_KEY_RELEASE_S3: | |
required: true | |
jobs: | |
export-notarized-app: | |
name: Export Notarized App | |
runs-on: macos-14-xlarge | |
outputs: | |
app-version: ${{ steps.set-outputs.outputs.app-version }} | |
app-name: ${{ steps.set-outputs.outputs.app-name }} | |
upload-to: ${{ steps.is-official-release.outputs.upload-to }} | |
env: | |
release-type: ${{ github.event.inputs.release-type || inputs.release-type }} | |
asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} | |
branch: ${{ inputs.branch || github.ref_name }} | |
steps: | |
- name: Check out the code | |
uses: actions/checkout@v4 | |
with: | |
submodules: recursive | |
ref: ${{ env.branch }} | |
- name: Set up fastlane | |
run: bundle install | |
- name: Check if this is an official release build | |
id: is-official-release | |
env: | |
is-official-release: ${{ env.release-type == 'release' && (startsWith(env.branch, 'release') || startsWith(env.branch, 'hotfix')) }} | |
run: | | |
if [[ "${{ env.is-official-release }}" == "true" ]]; then | |
echo "upload-to=s3" >> $GITHUB_OUTPUT | |
echo "upload-to=s3" >> $GITHUB_ENV | |
elif [[ -n "${{ env.asana-task-url }}" ]]; then | |
echo "upload-to=asana" >> $GITHUB_OUTPUT | |
echo "upload-to=asana" >> $GITHUB_ENV | |
fi | |
- name: Install Apple Developer ID Application certificate | |
uses: ./.github/actions/install-certs-and-profiles | |
with: | |
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} | |
P12_PASSWORD: ${{ secrets.P12_PASSWORD }} | |
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
APPSTORE_CI_PROVISION_PROFILE_BASE64: ${{ secrets.APPSTORE_CI_PROVISION_PROFILE_BASE64 }} | |
CI_PROVISION_PROFILE_BASE64: ${{ secrets.CI_PROVISION_PROFILE_BASE64 }} | |
DBP_AGENT_APPSTORE_CI_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_APPSTORE_CI_PROVISION_PROFILE_BASE64 }} | |
DBP_AGENT_CI_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_CI_PROVISION_PROFILE_BASE64 }} | |
DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} | |
DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.DBP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} | |
INTEGRATION_TESTS_APPSTORE_CI_PROVISION_PROFILE_BASE64: ${{ secrets.INTEGRATION_TESTS_APPSTORE_CI_PROVISION_PROFILE_BASE64 }} | |
INTEGRATION_TESTS_CI_PROVISION_PROFILE_BASE64: ${{ secrets.INTEGRATION_TESTS_CI_PROVISION_PROFILE_BASE64 }} | |
NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64 }} | |
NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64 }} | |
NETP_NOTIFICATIONS_CI_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_CI_PROVISION_PROFILE_BASE64 }} | |
NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64 }} | |
NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64 }} | |
NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64 }} | |
NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64 }} | |
RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_PROVISION_PROFILE_BASE64 }} | |
REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} | |
UNIT_TESTS_APPSTORE_CI_PROVISION_PROFILE_BASE64: ${{ secrets.UNIT_TESTS_APPSTORE_CI_PROVISION_PROFILE_BASE64 }} | |
UNIT_TESTS_CI_PROVISION_PROFILE_BASE64: ${{ secrets.UNIT_TESTS_CI_PROVISION_PROFILE_BASE64 }} | |
VPN_APPEX_APPSTORE_CI_PROVISION_PROFILE_BASE64: ${{ secrets.VPN_APPEX_APPSTORE_CI_PROVISION_PROFILE_BASE64 }} | |
VPN_APP_APPSTORE_CI_PROVISION_PROFILE_BASE64: ${{ secrets.VPN_APP_APPSTORE_CI_PROVISION_PROFILE_BASE64 }} | |
VPN_APP_CI_PROVISION_PROFILE_BASE64: ${{ secrets.VPN_APP_CI_PROVISION_PROFILE_BASE64 }} | |
VPN_PROXY_EXTENSION_CI_PROVISION_PROFILE_BASE64: ${{ secrets.VPN_PROXY_EXTENSION_CI_PROVISION_PROFILE_BASE64 }} | |
- name: Select Xcode | |
run: sudo xcode-select -s /Applications/Xcode_$(<.xcode-version).app/Contents/Developer | |
- name: Archive and notarize the app | |
id: archive | |
env: | |
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} | |
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} | |
APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} | |
ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
run: | | |
# import API Key from secrets | |
export APPLE_API_KEY_PATH="$RUNNER_TEMP/apple_api_key.pem" | |
echo -n "$APPLE_API_KEY_BASE64" | base64 --decode -o $APPLE_API_KEY_PATH | |
if [[ "${{ runner.debug }}" == "1" ]]; then | |
./scripts/archive.sh ${{ env.release-type }} -r | |
else | |
./scripts/archive.sh ${{ env.release-type }} | |
fi | |
- name: Set app name and version | |
id: set-outputs | |
run: | | |
echo "app-version=${{ env.app-version }}" >> $GITHUB_OUTPUT | |
echo "app-name=${{ env.app-name }}" >> $GITHUB_OUTPUT | |
echo "dsym-name=DuckDuckGo-${{ env.app-version }}-dSYM.zip" >> $GITHUB_OUTPUT | |
- name: Upload app artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.app | |
path: ${{ github.workspace }}/release/DuckDuckGo-${{ env.app-version }}.zip | |
- name: Upload dSYMs artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: DuckDuckGo-${{ env.release-type }}-dSYM-${{ env.app-version }} | |
path: ${{ github.workspace }}/release/${{ steps.set-outputs.outputs.dsym-name }} | |
- name: Upload dSYMs to S3 | |
id: upload-dsyms-to-s3 | |
if: ${{ env.upload-to == 's3' }} | |
env: | |
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }} | |
DSYM_S3_PATH: s3://${{ vars.DSYM_BUCKET_NAME }}/${{ vars.DSYM_BUCKET_PREFIX }}/${{ steps.set-outputs.outputs.dsym-name }} | |
run: | | |
echo "dsym-s3-path=${DSYM_S3_PATH}" >> $GITHUB_OUTPUT | |
aws s3 cp ${{ github.workspace }}/release/${{ steps.set-outputs.outputs.dsym-name }} ${{ env.DSYM_S3_PATH }} | |
- name: Report success | |
if: ${{ env.upload-to == 's3' }} | |
uses: ./.github/actions/asana-log-message | |
env: | |
DSYM_S3_PATH: ${{ steps.upload-dsyms-to-s3.outputs.dsym-s3-path }} | |
TAG: ${{ env.app-version }} | |
WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
with: | |
task-url: ${{ env.asana-task-url }} | |
access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
template-name: debug-symbols-uploaded | |
create-dmg: | |
name: Create DMG | |
needs: export-notarized-app | |
if: ${{ github.event.inputs.create-dmg == true || inputs.create-dmg == true }} | |
# use macos-12 for creating DMGs as macos-13 beta runners can't run AppleScript: https://app.asana.com/0/0/1204523592790998/f | |
runs-on: macos-12 | |
env: | |
app-version: ${{ needs.export-notarized-app.outputs.app-version }} | |
app-name: ${{ needs.export-notarized-app.outputs.app-name }} | |
asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} | |
upload-to: ${{ needs.export-notarized-app.outputs.upload-to }} | |
release-type: ${{ github.event.inputs.release-type || inputs.release-type }} | |
steps: | |
- name: Check out the code | |
uses: actions/checkout@v4 | |
with: | |
submodules: recursive | |
ref: ${{ inputs.branch || github.ref_name }} | |
sparse-checkout: | | |
.github | |
Gemfile | |
Gemfile.lock | |
fastlane | |
- name: Set up fastlane | |
run: bundle install | |
- name: Fetch app bundle | |
uses: actions/download-artifact@v4 | |
with: | |
name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.app | |
path: ${{ github.workspace }}/dmg | |
- name: Extract app bundle | |
run: | | |
ditto -xk DuckDuckGo-${{ env.app-version }}.zip . | |
rm -f DuckDuckGo-${{ env.app-version }}.zip | |
working-directory: ${{ github.workspace }}/dmg | |
- name: Install create-dmg | |
run: brew install create-dmg | |
- name: Create DMG | |
id: create-dmg | |
env: | |
GH_TOKEN: ${{ github.token }} | |
run: | | |
dmg="duckduckgo-${{ env.app-version }}.dmg" | |
curl -fLSs $(gh api https://api.github.com/repos/${{ github.repository }}/contents/scripts/assets/dmg-background.png?ref=${{ github.ref }} --jq .download_url) \ | |
--output dmg-background.png | |
create-dmg --volname "${{ env.app-name }}" \ | |
--icon "${{ env.app-name }}.app" 140 160 \ | |
--background "dmg-background.png" \ | |
--window-size 600 400 \ | |
--icon-size 120 \ | |
--app-drop-link 430 160 "${dmg}" \ | |
"dmg" | |
echo "dmg=${dmg}" >> $GITHUB_OUTPUT | |
- name: Upload DMG artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.dmg | |
path: ${{ github.workspace }}/${{ steps.create-dmg.outputs.dmg }} | |
- name: Upload DMG to S3 | |
if: ${{ env.upload-to == 's3' }} | |
env: | |
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID_RELEASE_S3 }} | |
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY_RELEASE_S3 }} | |
AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }} | |
RELEASE_BUCKET_NAME: ${{ vars.RELEASE_BUCKET_NAME }} | |
RELEASE_BUCKET_PREFIX: ${{ vars.RELEASE_BUCKET_PREFIX }} | |
run: | | |
aws s3 cp \ | |
${{ github.workspace }}/${{ steps.create-dmg.outputs.dmg }} \ | |
s3://${{ env.RELEASE_BUCKET_NAME }}/${{ env.RELEASE_BUCKET_PREFIX }}/ \ | |
--acl public-read | |
- name: Report success | |
if: ${{ env.upload-to == 's3' }} | |
uses: ./.github/actions/asana-log-message | |
env: | |
DMG_URL: ${{ vars.DMG_URL_ROOT }}${{ steps.create-dmg.outputs.dmg }} | |
TAG: ${{ env.app-version }} | |
WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
with: | |
task-url: ${{ env.asana-task-url }} | |
access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
template-name: dmg-uploaded | |
- name: Extract Asana Task ID | |
id: task-id | |
if: ${{ env.upload-to == 'asana' }} | |
run: bundle exec fastlane run asana_extract_task_id task_url:"${{ env.asana-task-url }}" | |
- name: Upload DMG to Asana | |
if: ${{ env.upload-to == 'asana' }} | |
env: | |
ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
run: | | |
bundle exec fastlane run asana_upload \ | |
file_name:"${{ github.workspace }}/duckduckgo-${{ env.app-version }}.dmg" \ | |
task_id:"${{ steps.task-id.outputs.asana_task_id }}" | |
mattermost: | |
name: Send Mattermost message | |
needs: [export-notarized-app, create-dmg] | |
if: always() | |
runs-on: ubuntu-latest | |
env: | |
success: ${{ (needs.export-notarized-app.result == 'success') && (needs.create-dmg.result == 'success' || needs.create-dmg.result == 'skipped') }} | |
failure: ${{ (needs.export-notarized-app.result == 'failure') || (needs.create-dmg.result == 'failure') }} | |
steps: | |
- name: Send Mattermost message | |
if: ${{ env.success || env.failure }} # Don't execute when cancelled | |
env: | |
ASANA_TASK_URL: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} | |
GH_TOKEN: ${{ github.token }} | |
RELEASE_TYPE: ${{ github.event.inputs.release-type || inputs.release-type }} | |
WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
run: | | |
curl -fLSs $(gh api https://api.github.com/repos/${{ github.repository }}/contents/scripts/assets/release-mm-template.json?ref=${{ github.ref }} --jq .download_url) \ | |
--output message-template.json | |
export MM_USER_HANDLE=$(base64 -d <<< ${{ secrets.MM_HANDLES_BASE64 }} | jq ".${{ github.actor }}" | tr -d '"') | |
if [[ -z "${MM_USER_HANDLE}" ]]; then | |
echo "Mattermost user handle not known for ${{ github.actor }}, skipping sending message" | |
else | |
if [[ -n "${ASANA_TASK_URL}" ]]; then | |
export ASANA_LINK=" | [:asana: Asana task](${ASANA_TASK_URL})" | |
fi | |
if [[ "${{ env.success }}" == "true" ]]; then | |
status="success" | |
else | |
status="failure" | |
fi | |
curl -s -H 'Content-type: application/json' \ | |
-d "$(envsubst < message-template.json | jq ".${status}")" \ | |
${{ secrets.MM_WEBHOOK_URL }} | |
fi |