From 6cdf1d5a153aef2845eee6e25103abfa00aaf575 Mon Sep 17 00:00:00 2001 From: Itms Date: Tue, 31 Dec 2024 13:26:55 +0100 Subject: [PATCH] Sign and notarize macOS bundles (cherry picked from commit 2482ecf9fed463ce26bbae370259bd1654978713) Signed-off-by: Itms --- build/jenkins/pipelines/bundles.Jenkinsfile | 35 +++++++++++++++++++-- source/lib/sysdep/os/osx/osx_bundle.h | 4 +-- source/tools/dist/0ad.entitlements | 12 +++++++ source/tools/dist/build-osx-bundle.py | 32 +++++++++++++++++-- 4 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 source/tools/dist/0ad.entitlements diff --git a/build/jenkins/pipelines/bundles.Jenkinsfile b/build/jenkins/pipelines/bundles.Jenkinsfile index 192791e3c0..8b677f4f71 100644 --- a/build/jenkins/pipelines/bundles.Jenkinsfile +++ b/build/jenkins/pipelines/bundles.Jenkinsfile @@ -69,7 +69,22 @@ pipeline { stage("Create Native macOS Bundle") { steps { - sh "/opt/wfg/venv/bin/python3 source/tools/dist/build-osx-bundle.py --min_osx=${env.MIN_OSX_VERSION} ${params.BUNDLE_VERSION}" + withCredentials([ + string(credentialsId: 'apple-keychain', variable: 'KEYCHAIN_PW'), + string(credentialsId: 'apple-signing', variable: 'SIGNKEY_SHA'), + usernamePassword(credentialsId: 'apple-notarization', passwordVariable: 'NOTARIZATION_PW', usernameVariable: 'NOTARIZATION_USER')]) + { + sh ''' + security unlock-keychain -p ${KEYCHAIN_PW} login.keychain + /opt/wfg/venv/bin/python3 source/tools/dist/build-osx-bundle.py \ + --min_osx=${MIN_OSX_VERSION} \ + -s ${SIGNKEY_SHA} \ + --notarytool_user=${NOTARIZATION_USER} \ + --notarytool_team=P7YF26GARW \ + --notarytool_password=${NOTARIZATION_PW} \ + ${BUNDLE_VERSION} + ''' + } } } @@ -90,7 +105,23 @@ pipeline { stage("Create Intel macOS Bundle") { steps { - sh "/opt/wfg/venv/bin/python3 source/tools/dist/build-osx-bundle.py --architecture=x86_64 --min_osx=${env.MIN_OSX_VERSION} ${params.BUNDLE_VERSION}" + withCredentials([ + string(credentialsId: 'apple-keychain', variable: 'KEYCHAIN_PW'), + string(credentialsId: 'apple-signing', variable: 'SIGNKEY_SHA'), + usernamePassword(credentialsId: 'apple-notarization', passwordVariable: 'NOTARIZATION_PW', usernameVariable: 'NOTARIZATION_USER')]) + { + sh ''' + security unlock-keychain -p ${KEYCHAIN_PW} login.keychain + /opt/wfg/venv/bin/python3 source/tools/dist/build-osx-bundle.py \ + --architecture=x86_64 \ + --min_osx=${MIN_OSX_VERSION} \ + -s ${SIGNKEY_SHA} \ + --notarytool_user=${NOTARIZATION_USER} \ + --notarytool_team=P7YF26GARW \ + --notarytool_password=${NOTARIZATION_PW} \ + ${BUNDLE_VERSION} + ''' + } } } diff --git a/source/lib/sysdep/os/osx/osx_bundle.h b/source/lib/sysdep/os/osx/osx_bundle.h index 30b9424d1a..1f91e40c1d 100644 --- a/source/lib/sysdep/os/osx/osx_bundle.h +++ b/source/lib/sysdep/os/osx/osx_bundle.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2025 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -32,7 +32,7 @@ * Check if app is running in a valid bundle * * @return true if valid bundle reference was found matching identifier - * property "com.wildfiregames.0ad" + * property "com.wildfiregames.play0ad" */ bool osx_IsAppBundleValid(); diff --git a/source/tools/dist/0ad.entitlements b/source/tools/dist/0ad.entitlements new file mode 100644 index 0000000000..cf71cb9c5b --- /dev/null +++ b/source/tools/dist/0ad.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/source/tools/dist/build-osx-bundle.py b/source/tools/dist/build-osx-bundle.py index 9181562787..7f16545402 100755 --- a/source/tools/dist/build-osx-bundle.py +++ b/source/tools/dist/build-osx-bundle.py @@ -15,6 +15,7 @@ import glob import os import plistlib import shutil +import subprocess import dmgbuild @@ -25,7 +26,11 @@ parser.add_argument('--architecture', help='aarch64 (arm64) or x86_64 (amd64)', parser.add_argument('--min_osx', help='Minimum supported OSX version', default='10.12') parser.add_argument('--bundle_identifier', help='Bundle identifier', - default='com.wildfiregames.0ad') + default='com.wildfiregames.play0ad') +parser.add_argument('-s', '--signkey', help='Signature key sha sum') +parser.add_argument('--notarytool_user', help='Apple ID user for notarization') +parser.add_argument('--notarytool_team', help='Team ID for notarization') +parser.add_argument('--notarytool_password', help='App password for notarization') parser.add_argument('--dev', help='Turn on dev mode, which isn\'t fit for release but faster', action="store_true") args = parser.parse_args() @@ -35,6 +40,11 @@ BUNDLE_IDENTIFIER = args.bundle_identifier BUNDLE_VERSION = args.bundle_version BUNDLE_MIN_OSX_VERSION = args.min_osx +SIGNKEY = args.signkey +NOTARYTOOL_USER = args.notarytool_user +NOTARYTOOL_TEAM = args.notarytool_team +NOTARYTOOL_PASSWORD = args.notarytool_password + BUNDLE_DMG_NAME = f"0ad-{BUNDLE_VERSION}-macos-{ARCH}" BUNDLE_OUTPUT = "0 A.D..app" BUNDLE_CONTENTS = BUNDLE_OUTPUT + "/Contents" @@ -65,6 +75,12 @@ shutil.copy("binaries/system/libAtlasUI.dylib", BUNDLE_FRAMEWORKS) shutil.copy("binaries/system/libCollada.dylib", BUNDLE_FRAMEWORKS) shutil.copy("binaries/system/libMoltenVK.dylib", BUNDLE_FRAMEWORKS) +if not args.dev: + print("Signing libs") + subprocess.run(["codesign", "-s", SIGNKEY, "-f", "--timestamp", BUNDLE_FRAMEWORKS + "/libAtlasUI.dylib"], check=True) + subprocess.run(["codesign", "-s", SIGNKEY, "-f", "--timestamp", BUNDLE_FRAMEWORKS + "/libCollada.dylib"], check=True) + subprocess.run(["codesign", "-s", SIGNKEY, "-f", "--timestamp", BUNDLE_FRAMEWORKS + "/libMoltenVK.dylib"], check=True) + if not args.dev: print("Copying archived game data from archives/") for mod in glob.glob("archives/*/"): @@ -138,6 +154,9 @@ if args.dev: print(f"Dev mode bundle complete, located at {BUNDLE_OUTPUT}") exit(0) +print("Signing bundle") +subprocess.run(["codesign", "-s", SIGNKEY, "-f", "--timestamp", "-o", "runtime", "--entitlements", "source/tools/dist/0ad.entitlements", BUNDLE_OUTPUT], check=True) + print("Creating .dmg") # Package the app into a dmg @@ -151,4 +170,13 @@ dmgbuild.build_dmg( "icon": "build/resources/0ad.icns" }) -print(f"Bundle complete! Located in {BUNDLE_OUTPUT}, compressed as {BUNDLE_DMG_NAME}.dmg.") +print("Signing .dmg") +subprocess.run(["codesign", "-s", SIGNKEY, "-f", "--timestamp", "-i", BUNDLE_IDENTIFIER, BUNDLE_DMG_NAME + ".dmg"], check=True) + +print("Notarizing .dmg") +subprocess.run(["xcrun", "notarytool", "submit", BUNDLE_DMG_NAME + ".dmg", "--apple-id", NOTARYTOOL_USER, "--team-id", NOTARYTOOL_TEAM, "--password", NOTARYTOOL_PASSWORD, "--wait"], check=True) + +print("Stapling notarization ticket") +subprocess.run(["xcrun", "stapler", "staple", BUNDLE_DMG_NAME + ".dmg"], check=True) + +print(f"Bundle complete! Installer located at {BUNDLE_DMG_NAME}.dmg.")