Make Notarized DMG Release #239
Workflow file for this run
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 | |
- review-sandbox | |
- release-sandbox | |
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: true | |
type: string | |
secrets: | |
BUILD_CERTIFICATE_BASE64: | |
required: true | |
P12_PASSWORD: | |
required: true | |
KEYCHAIN_PASSWORD: | |
required: true | |
REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_START_VPN_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_STOP_VPN_PROVISION_PROFILE_BASE64: | |
required: true | |
NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: | |
required: true | |
SSH_PRIVATE_KEY_FIND_IN_PAGE: | |
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 | |
jobs: | |
export-notarized-app: | |
name: Export Notarized App | |
runs-on: macos-13 | |
outputs: | |
app-version: ${{ steps.set-outputs.outputs.app-version }} | |
app-name: ${{ steps.set-outputs.outputs.app-name }} | |
env: | |
release-type: ${{ github.event.inputs.release-type || inputs.release-type }} | |
asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} | |
steps: | |
- name: Install Apple Developer ID Application certificate | |
env: | |
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} | |
P12_PASSWORD: ${{ secrets.P12_PASSWORD }} | |
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
REVIEW_PROVISION_PROFILE_BASE64: ${{ secrets.REVIEW_PROVISION_PROFILE_BASE64 }} | |
RELEASE_PROVISION_PROFILE_BASE64: ${{ secrets.RELEASE_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 }} | |
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_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_START_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_START_VPN_PROVISION_PROFILE_BASE64 }} | |
NETP_STOP_VPN_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_STOP_VPN_PROVISION_PROFILE_BASE64 }} | |
NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64: ${{ secrets.NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64 }} | |
run: | | |
# create variables | |
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 | |
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
REVIEW_PP_PATH=$RUNNER_TEMP/review_pp.provisionprofile | |
RELEASE_PP_PATH=$RUNNER_TEMP/release_pp.provisionprofile | |
NETP_SYSEX_RELEASE_PP_PATH=$RUNNER_TEMP/netp_sysex_release_pp.provisionprofile | |
NETP_SYSEX_REVIEW_PP_PATH=$RUNNER_TEMP/netp_sysex_review_pp.provisionprofile | |
NETP_AGENT_RELEASE_PP_PATH=$RUNNER_TEMP/netp_agent_release_pp.provisionprofile | |
NETP_AGENT_REVIEW_PP_PATH=$RUNNER_TEMP/netp_agent_review_pp.provisionprofile | |
NETP_NOTIFICATIONS_RELEASE_PP_PATH=$RUNNER_TEMP/netp_notifications_release_pp.provisionprofile | |
NETP_NOTIFICATIONS_REVIEW_PP_PATH=$RUNNER_TEMP/netp_notifications_review_pp.provisionprofile | |
NETP_START_VPN_PP_PATH=$RUNNER_TEMP/netp_start_vpn_pp.provisionprofile | |
NETP_STOP_VPN_PP_PATH=$RUNNER_TEMP/netp_stop_vpn_pp.provisionprofile | |
NETP_ENABLE_ON_DEMAND_PP_PATH=$RUNNER_TEMP/netp_enable_on_demand_pp.provisionprofile | |
# import certificate from secrets | |
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH | |
echo -n "$REVIEW_PROVISION_PROFILE_BASE64" | base64 --decode -o $REVIEW_PP_PATH | |
echo -n "$RELEASE_PROVISION_PROFILE_BASE64" | base64 --decode -o $RELEASE_PP_PATH | |
echo -n "$NETP_SYSEX_RELEASE_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_SYSEX_RELEASE_PP_PATH | |
echo -n "$NETP_SYSEX_REVIEW_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_SYSEX_REVIEW_PP_PATH | |
echo -n "$NETP_AGENT_RELEASE_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_AGENT_RELEASE_PP_PATH | |
echo -n "$NETP_AGENT_REVIEW_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_AGENT_REVIEW_PP_PATH | |
echo -n "$NETP_NOTIFICATIONS_RELEASE_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_NOTIFICATIONS_RELEASE_PP_PATH | |
echo -n "$NETP_NOTIFICATIONS_REVIEW_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_NOTIFICATIONS_REVIEW_PP_PATH | |
echo -n "$NETP_START_VPN_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_START_VPN_PP_PATH | |
echo -n "$NETP_STOP_VPN_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_STOP_VPN_PP_PATH | |
echo -n "$NETP_ENABLE_ON_DEMAND_PROVISION_PROFILE_BASE64" | base64 --decode -o $NETP_ENABLE_ON_DEMAND_PP_PATH | |
# create temporary keychain | |
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
# import certificate to keychain | |
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
security list-keychain -d user -s $KEYCHAIN_PATH | |
# apply provisioning profile | |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles | |
cp $REVIEW_PP_PATH \ | |
$RELEASE_PP_PATH \ | |
$NETP_SYSEX_RELEASE_PP_PATH \ | |
$NETP_SYSEX_REVIEW_PP_PATH \ | |
$NETP_AGENT_RELEASE_PP_PATH \ | |
$NETP_AGENT_REVIEW_PP_PATH \ | |
$NETP_NOTIFICATIONS_RELEASE_PP_PATH \ | |
$NETP_NOTIFICATIONS_REVIEW_PP_PATH \ | |
$NETP_START_VPN_PP_PATH \ | |
$NETP_STOP_VPN_PP_PATH \ | |
$NETP_ENABLE_ON_DEMAND_PP_PATH \ | |
~/Library/MobileDevice/Provisioning\ Profiles | |
- name: Register SSH keys for submodules access | |
uses: webfactory/[email protected] | |
with: | |
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_FIND_IN_PAGE }} | |
- name: Check out the code | |
uses: actions/checkout@v3 | |
with: | |
submodules: recursive | |
- name: Set cache key hash | |
run: | | |
has_only_tags=$(jq '[ .pins[].state | has("version") ] | all' DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved) | |
if [[ "$has_only_tags" == "true" ]]; then | |
echo "cache_key_hash=${{ hashFiles('DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }}" >> $GITHUB_ENV | |
else | |
echo "Package.resolved contains dependencies specified by branch or commit, skipping cache." | |
fi | |
- name: Cache SPM | |
if: env.cache_key_hash | |
uses: actions/cache@v3 | |
with: | |
path: DerivedData/SourcePackages | |
key: ${{ runner.os }}-spm-${{ env.cache_key_hash }} | |
restore-keys: | | |
${{ runner.os }}-spm- | |
- name: Install xcbeautify | |
continue-on-error: true | |
run: brew install xcbeautify | |
- 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 | |
./scripts/archive.sh ${{ env.release-type }} | |
- 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 | |
- name: Upload app artifact | |
uses: actions/upload-artifact@v3 | |
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@v3 | |
with: | |
name: DuckDuckGo-${{ env.release-type }}-dSYM-${{ env.app-version }} | |
path: ${{ github.workspace }}/release/DuckDuckGo-${{ env.app-version }}-dSYM.zip | |
- name: Upload dSYMs to Asana | |
if: env.asana-task-url | |
uses: ./.github/actions/asana-upload | |
with: | |
access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
file-name: ${{ github.workspace }}/release/DuckDuckGo-${{ env.app-version }}-dSYM.zip | |
task-url: ${{ env.asana-task-url }} | |
- name: Upload dSYMs to S3 | |
if: ${{ env.release-type == 'release' && (startsWith(github.ref_name, 'release') || startsWith(github.ref_name, 'hotfix')) }} | |
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_BUCKET_NAME: ${{ vars.DSYM_BUCKET_NAME }} | |
DSYM_BUCKET_PREFIX: ${{ vars.DSYM_BUCKET_PREFIX }} | |
run: | | |
aws s3 cp \ | |
${{ github.workspace }}/release/DuckDuckGo-${{ env.app-version }}-dSYM.zip \ | |
s3://${{ env.DSYM_BUCKET_NAME }}/${{ env.DSYM_BUCKET_PREFIX }}/ | |
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 }} | |
release-type: ${{ github.event.inputs.release-type || inputs.release-type }} | |
steps: | |
- name: Fetch app bundle | |
uses: actions/download-artifact@v3 | |
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 | |
env: | |
GH_TOKEN: ${{ github.token }} | |
run: | | |
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 "duckduckgo-${{ env.app-version }}.dmg" \ | |
"dmg" | |
- name: Upload DMG artifact | |
uses: actions/upload-artifact@v3 | |
with: | |
name: DuckDuckGo-${{ env.release-type }}-${{ env.app-version }}.dmg | |
path: ${{ github.workspace }}/duckduckgo*-${{ env.app-version }}.dmg | |
- name: Fetch Upload to Asana action | |
if: env.asana-task-url | |
env: | |
GH_TOKEN: ${{ github.token }} | |
run: | | |
mkdir -p "${{ github.workspace }}/.github/actions/asana-upload" | |
curl -fLSs $(gh api https://api.github.com/repos/${{ github.repository }}/contents/.github/actions/asana-upload/action.yml?ref=${{ github.ref }} --jq .download_url) \ | |
--output "${{ github.workspace }}/.github/actions/asana-upload/action.yml" | |
- name: Upload DMG to Asana | |
if: env.asana-task-url | |
uses: ./.github/actions/asana-upload | |
with: | |
access-token: ${{ secrets.ASANA_ACCESS_TOKEN }} | |
file-name: ${{ github.workspace }}/duckduckgo-${{ env.app-version }}.dmg | |
task-url: ${{ env.asana-task-url }} | |
mattermost: | |
name: Send Mattermost message | |
needs: [export-notarized-app, create-dmg] | |
if: ${{ always() && (needs.export-notarized-app.result == 'success') && (needs.create-dmg.result == 'success' || needs.create-dmg.result == 'skipped') }} | |
runs-on: ubuntu-latest | |
steps: | |
- name: Send Mattermost message | |
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 | |
curl -s -H 'Content-type: application/json' \ | |
-d "$(envsubst < message-template.json)" \ | |
${{ secrets.MM_WEBHOOK_URL }} | |
fi |