Skip to content

Make Notarized DMG Release #236

Make Notarized DMG Release

Make Notarized DMG Release #236

Workflow file for this run

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