diff --git a/.github/macos-installer/Makefile b/.github/macos-installer/Makefile index df339bd921df23..3231c4078c88c6 100644 --- a/.github/macos-installer/Makefile +++ b/.github/macos-installer/Makefile @@ -24,7 +24,12 @@ ARTIFACTDIR := build_artifacts SUBMAKE := $(MAKE) C_INCLUDE_PATH="$(C_INCLUDE_PATH)" CPLUS_INCLUDE_PATH="$(CPLUS_INCLUDE_PATH)" LD_LIBRARY_PATH="$(LD_LIBRARY_PATH)" TARGET_FLAGS="$(TARGET_FLAGS)" CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" NO_GETTEXT=1 NO_DARWIN_PORTS=1 prefix=$(GIT_PREFIX) GIT_BUILT_FROM_COMMIT="$(GIT_BUILT_FROM_COMMIT)" DESTDIR=$(DESTDIR) CORES := $(shell bash -c "sysctl hw.ncpu | awk '{print \$$2}'") -.PHONY: image pkg payload +# Guard against environment variables +APPLE_APP_IDENTITY = +APPLE_INSTALLER_IDENTITY = +APPLE_KEYCHAIN_PROFILE = + +.PHONY: image pkg payload codesign notarize .SECONDARY: @@ -102,7 +107,11 @@ disk-image/VERSION-$(VERSION)-$(ARCH_CODE): touch "$@" disk-image/git-$(VERSION)-$(BUILD_CODE).pkg: disk-image/VERSION-$(VERSION)-$(ARCH_CODE) symlinks - pkgbuild --identifier com.git.pkg --version $(VERSION) --root $(ARTIFACTDIR)$(PREFIX) --scripts assets/scripts --install-location $(PREFIX) --component-plist ./assets/git-components.plist disk-image/git-$(VERSION)-$(BUILD_CODE).pkg + pkgbuild --identifier com.git.pkg --version $(VERSION) \ + --root $(ARTIFACTDIR)$(PREFIX) --scripts assets/scripts \ + --install-location $(PREFIX) --component-plist \ + ${APPLE_INSTALLER_IDENTITY:+"--sign"} ${APPLE_INSTALLER_IDENTITY:+"$APPLE_INSTALLER_IDENTITY"} \ + ./assets/git-components.plist disk-image/git-$(VERSION)-$(BUILD_CODE).pkg git-%-$(BUILD_CODE).dmg: hdiutil create git-$(VERSION)-$(BUILD_CODE).uncompressed.dmg -fs HFS+ -srcfolder disk-image -volname "Git $(VERSION) Intel $(ARCH)" -ov @@ -114,3 +123,21 @@ payload: $(BUILD_DIR)/git-$(VERSION)/osx-installed $(BUILD_DIR)/git-$(VERSION)/o pkg: disk-image/git-$(VERSION)-$(BUILD_CODE).pkg image: git-$(VERSION)-$(BUILD_CODE).dmg + +ifdef APPLE_APP_IDENTITY +codesign: payload + @.github/scripts/codesign.sh --payload="stage/git-$(BUILD_CODE)-$(VERSION)" \ + --identity="$(APPLE_APP_IDENTITY)" \ + --entitlements="$(CURDIR)/.github/macos-installer/entitlements.xml" +endif + +# Notarization can only happen if the package is fully signed +ifdef APPLE_INSTALLER_IDENTITY +ifdef APPLE_KEYCHAIN_PROFILE +.PHONY: notarize +notarize: pkg + @.github/scripts/notarize.sh \ + --package="disk-image/git-$(VERSION)-$(BUILD_CODE).pkg" \ + --keychain-profile="$(APPLE_KEYCHAIN_PROFILE)" +endif +endif diff --git a/.github/macos-installer/entitlements.xml b/.github/macos-installer/entitlements.xml new file mode 100644 index 00000000000000..46f675661149b6 --- /dev/null +++ b/.github/macos-installer/entitlements.xml @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + diff --git a/.github/scripts/codesign.sh b/.github/scripts/codesign.sh new file mode 100644 index 00000000000000..0d6f6ab66fcb07 --- /dev/null +++ b/.github/scripts/codesign.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +sign_directory () { + ( + cd $1 + for f in * + do + macho=$(file --mime $f | grep mach) + # Runtime sign dylibs and Mach-O binaries + if [[ $f == *.dylib ]] || [ ! -z "$macho" ]; + then + echo "Runtime Signing $f" + codesign -s "$IDENTITY" $f --timestamp --force --options=runtime --entitlements $ENTITLEMENTS_FILE + elif [ -d "$f" ]; + then + echo "Signing files in subdirectory $f" + sign_directory $f + + else + echo "Signing $f" + codesign -s "$IDENTITY" $f --timestamp --force + fi + done + ) +} + +for i in "$@" +do +case "$i" in + --payload=*) + SIGN_DIR="${i#*=}" + shift # past argument=value + ;; + --identity=*) + IDENTITY="${i#*=}" + shift # past argument=value + ;; + --entitlements=*) + ENTITLEMENTS_FILE="${i#*=}" + shift # past argument=value + ;; + *) + die "unknown option '$i'" + ;; +esac +done + +if [ -z "$SIGN_DIR" ]; then + echo "error: missing directory argument" + exit 1 +elif [ -z "$IDENTITY" ]; then + echo "error: missing signing identity argument" + exit 1 +elif [ -z "$ENTITLEMENTS_FILE" ]; then + echo "error: missing entitlements file argument" + exit 1 +fi + +echo "======== INPUTS ========" +echo "Directory: $SIGN_DIR" +echo "Signing identity: $IDENTITY" +echo "Entitlements: $ENTITLEMENTS_FILE" +echo "======== END INPUTS ========" + +sign_directory "$SIGN_DIR" diff --git a/.github/scripts/notarize.sh b/.github/scripts/notarize.sh new file mode 100644 index 00000000000000..9315d688afbd49 --- /dev/null +++ b/.github/scripts/notarize.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +for i in "$@" +do +case "$i" in + --package=*) + PACKAGE="${i#*=}" + shift # past argument=value + ;; + --keychain-profile=*) + KEYCHAIN_PROFILE="${i#*=}" + shift # past argument=value + ;; + *) + die "unknown option '$i'" + ;; +esac +done + +if [ -z "$PACKAGE" ]; then + echo "error: missing package argument" + exit 1 +elif [ -z "$KEYCHAIN_PROFILE" ]; then + echo "error: missing keychain profile argument" + exit 1 +fi + +# Exit as soon as any line fails +set -e + +# Send the notarization request +xcrun notarytool submit -v "$PACKAGE" -p "$KEYCHAIN_PROFILE" --wait + +# Staple the notarization ticket (to allow offline installation) +xcrun stapler staple -v "$PACKAGE" diff --git a/.github/workflows/macos-updates.yml b/.github/workflows/macos-updates.yml new file mode 100644 index 00000000000000..44454f53de5b72 --- /dev/null +++ b/.github/workflows/macos-updates.yml @@ -0,0 +1,168 @@ +name: build-git-installers + +on: + push: + tags: + - 'v[0-9]*vfs*' # matches "vvfs" + +jobs: + # Check prerequisites for the workflow + prereqs: + runs-on: ubuntu-latest + environment: release + env: + AZ_SUB: ${{ secrets.AZURE_SUBSCRIPTION }} + AZ_CREDS: ${{ secrets.AZURE_CREDENTIALS }} + outputs: + tag_name: ${{ steps.tag.outputs.name }} # The full name of the tag, e.g. v2.32.0.vfs.0.0 + tag_version: ${{ steps.tag.outputs.version }} # The version number (without preceding "v"), e.g. 2.32.0.vfs.0.0 + deb_signable: ${{ steps.deb.outputs.signable }} # Whether the credentials needed to sign the .deb package are available + steps: + - name: Validate tag + run: | + echo "$GITHUB_REF" | + grep '^refs/tags/v2\.\(0\|[1-9][0-9]*\)\.\(0\|[1-9][0-9]*\)\.vfs\.0\.\(0\|[1-9][0-9]*\)$' || { + echo "::error::${GITHUB_REF#refs/tags/} is not of the form v2...vfs.0." >&2 + exit 1 + } + - name: Determine tag to build + run: | + echo "name=${GITHUB_REF#refs/tags/}" >>$GITHUB_OUTPUT + echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT + id: tag + - name: Determine whether signing certificates are present + run: echo "signable=$([[ $AZ_SUB != '' && $AZ_CREDS != '' ]] && echo 'true' || echo 'false')" >>$GITHUB_OUTPUT + id: deb + - name: Clone git + uses: actions/checkout@v3 + - name: Validate the tag identified with trigger + run: | + die () { + echo "::error::$*" >&2 + exit 1 + } + + # `actions/checkout` only downloads the peeled tag (i.e. the commit) + git fetch origin +$GITHUB_REF:$GITHUB_REF + + # Verify that the tag is annotated + test $(git cat-file -t "$GITHUB_REF") == "tag" || die "Tag ${{ steps.tag.outputs.name }} is not annotated" + + # Verify tag follows rules in GIT-VERSION-GEN (i.e., matches the specified "DEF_VER" in + # GIT-VERSION-FILE) and matches tag determined from trigger + make GIT-VERSION-FILE + test "${{ steps.tag.outputs.version }}" == "$(sed -n 's/^GIT_VERSION = //p'< GIT-VERSION-FILE)" || die "GIT-VERSION-FILE tag does not match ${{ steps.tag.outputs.name }}" + # End check prerequisites for the workflow + + # Build and sign Mac OSX installers & upload artifacts + osx_build: + runs-on: macos-latest + needs: prereqs + env: + # `gettext` is keg-only + LDFLAGS: -L/usr/local/opt/gettext/lib + CFLAGS: -I/usr/local/opt/gettext/include + # To make use of the catalogs... + XML_CATALOG_FILES: /usr/local/etc/xml/catalog + VERSION: "${{ needs.prereqs.outputs.tag_version }}" + environment: release + steps: + - name: Check out repository + uses: actions/checkout@v3 + with: + path: 'git' + + - name: Install Git dependencies + run: | + set -x + brew install automake asciidoc xmlto docbook + brew link --force gettext + + - name: Set up signing/notarization infrastructure + env: + A1: ${{ secrets.APPLICATION_CERTIFICATE_BASE64 }} + A2: ${{ secrets.APPLICATION_CERTIFICATE_PASSWORD }} + I1: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }} + I2: ${{ secrets.INSTALLER_CERTIFICATE_PASSWORD }} + N1: ${{ secrets.APPLE_TEAM_ID }} + N2: ${{ secrets.APPLE_DEVELOPER_ID }} + N3: ${{ secrets.APPLE_DEVELOPER_PASSWORD }} + N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} + run: | + echo "Setting up signing certificates" + security create-keychain -p pwd $RUNNER_TEMP/buildagent.keychain + security default-keychain -s $RUNNER_TEMP/buildagent.keychain + security unlock-keychain -p pwd $RUNNER_TEMP/buildagent.keychain + + echo $A1 | base64 -D > $RUNNER_TEMP/cert.p12 + security import $RUNNER_TEMP/cert.p12 \ + -k $RUNNER_TEMP/buildagent.keychain \ + -P $A2 \ + -T /usr/bin/codesign + security set-key-partition-list \ + -S apple-tool:,apple:,codesign: \ + -s -k pwd \ + $RUNNER_TEMP/buildagent.keychain + + echo $I1 | base64 -D > $RUNNER_TEMP/cert.p12 + security import $RUNNER_TEMP/cert.p12 \ + -k $RUNNER_TEMP/buildagent.keychain \ + -P $I2 \ + -T /usr/bin/productbuild + security set-key-partition-list \ + -S apple-tool:,apple:,productbuild: \ + -s -k pwd \ + $RUNNER_TEMP/buildagent.keychain + + echo "Setting up notarytool" + xcrun notarytool store-credentials \ + --team-id $N1 \ + --apple-id $N2 \ + --password $N3 \ + "$N4" + + - name: Build the release artifact + env: + A3: ${{ secrets.APPLE_APPLICATION_SIGNING_IDENTITY }} + I3: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }} + N4: ${{ secrets.APPLE_KEYCHAIN_PROFILE }} + run: | + # Configure the environment + set -x + PATH=/usr/local/bin:$PATH + export CURL_LDFLAGS=$(curl-config --libs) + + # Write to "version" file to force match with trigger payload version + echo "${{ needs.prereqs.outputs.tag_version }}" >>git/version + make -C git -j$(sysctl -n hw.physicalcpu) GIT-VERSION-FILE dist dist-doc + + export GIT_BUILT_FROM_COMMIT=$(gunzip -c git/git-$VERSION.tar.gz | git get-tar-commit-id) || + die "Could not determine commit for build" + + # Extract tarballs + mkdir payload manpages + tar -xvf git/git-$VERSION.tar.gz -C payload + tar -xvf git/git-manpages-$VERSION.tar.gz -C manpages + + # Build and codesign payload + make -C git/.github/macos-installer V=1 codesign \ + APPLE_APP_IDENTITY=echo "$A3" || die "Creating signed payload failed" + + # Build, sign, and notarize pkg + PATH=/usr/local/bin:$PATH \ + make -C git/.github/macos-installer V=1 notarize \ + APPLE_INSTALLER_IDENTITY=echo "$I3" APPLE_KEYCHAIN_PROFILE=echo "$N4" \ + || die "Creating signed and notarized pkg failed" + + # Move disk-image into the same directory as Makefile + mv disk-image git/.github/macos-installer/ + + # Create DMG + make -C git/.github/macos-installer V=1 image || die "Creating DMG failed" + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: osx-dmg + path: git/.github/macos-installer/*.dmg + # End build and sign Mac OSX installers