ghostty/.github/workflows/release-tip.yml
Jon Parise 074edd3065 macos: disable auto-updates for local builds
The auto-update prompt isn't useful for local (source) builds. Disable
it by default by setting Sparkle's SUEnableAutomaticChecks Info.plist
key to NO (false) for all build configurations.

We then selectively re-enable it by deleting that Info.plist key from
our release workflows. We delete the key instead of setting its value to
YES (true) to give us Sparkle's default behavior of prompting the user
to enable update checks on the second application launch. (YES tells
Sparkle to skip that prompt and silently enable update checks.)

See also: https://sparkle-project.org/documentation/customization/
2024-12-29 15:48:59 -05:00

696 lines
31 KiB
YAML

on:
workflow_run:
workflows: [Test]
types: [completed]
branches: [main]
workflow_dispatch: {}
name: Release Tip
# We must only run one release workflow at a time to prevent corrupting
# our release artifacts.
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
tag:
runs-on: namespace-profile-ghostty-sm
needs: [build-macos]
steps:
- uses: actions/checkout@v4
- name: Tip Tag
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -fa tip -m "Latest Continuous Release" ${GITHUB_SHA}
git push --force origin tip
sentry-dsym-debug-slow:
runs-on: namespace-profile-ghostty-sm
needs: [build-macos-debug-slow]
steps:
- uses: actions/checkout@v4
- name: Install sentry-cli
run: |
curl -sL https://sentry.io/get-cli/ | bash
- name: Download dSYM
run: |
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
curl -L https://tip.files.ghostty.dev/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow-dsym.zip > dsym.zip
- name: Upload dSYM to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
sentry-cli dif upload --project ghostty --wait dsym.zip
sentry-dsym-debug-fast:
runs-on: namespace-profile-ghostty-sm
needs: [build-macos-debug-fast]
steps:
- uses: actions/checkout@v4
- name: Install sentry-cli
run: |
curl -sL https://sentry.io/get-cli/ | bash
- name: Download dSYM
run: |
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
curl -L https://tip.files.ghostty.dev/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast-dsym.zip > dsym.zip
- name: Upload dSYM to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
sentry-cli dif upload --project ghostty --wait dsym.zip
sentry-dsym:
runs-on: namespace-profile-ghostty-sm
needs: [build-macos]
steps:
- uses: actions/checkout@v4
- name: Install sentry-cli
run: |
curl -sL https://sentry.io/get-cli/ | bash
- name: Download dSYM
run: |
GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)
curl -L https://tip.files.ghostty.dev/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-dsym.zip > dsym.zip
- name: Upload dSYM to Sentry
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
sentry-cli dif upload --project ghostty --wait dsym.zip
source-tarball:
if: |
${{
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.repository_owner == 'ghostty-org' &&
github.ref_name == 'main'
)
}}
runs-on: namespace-profile-ghostty-md
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Create Tarball
run: git archive --format=tgz --prefix=ghostty-source/ -o ghostty-source.tar.gz HEAD
- name: Sign Tarball
run: |
echo -n "${{ secrets.MINISIGN_KEY }}" > minisign.key
echo -n "${{ secrets.MINISIGN_PASSWORD }}" > minisign.password
nix develop -c minisign -S -m ghostty-source.tar.gz -s minisign.key < minisign.password
- name: Update Release
uses: softprops/action-gh-release@v2
with:
name: 'Ghostty Tip ("Nightly")'
prerelease: true
tag_name: tip
target_commitish: ${{ github.sha }}
files: |
ghostty-source.tar.gz
ghostty-source.tar.gz.minisig
token: ${{ secrets.GH_RELEASE_TOKEN }}
build-macos:
if: |
${{
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.repository_owner == 'ghostty-org' &&
github.ref_name == 'main'
)
}}
runs-on: namespace-profile-ghostty-macos
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Important so that build number generation works
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.6.3
run: |
mkdir -p .action/sparkle
cd .action/sparkle
curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
unzip sparkle.zip
echo "$(pwd)/bin" >> $GITHUB_PATH
# Load Build Number
- name: Build Number
run: |
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)" >> $GITHUB_ENV
# GhosttyKit is the framework that is built from Zig for our native
# Mac app to access. Build this in release mode.
- name: Build GhosttyKit
run: nix develop -c zig build -Doptimize=ReleaseFast
# The native app is built with native XCode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
# Nix breaks xcodebuild so this has to be run outside.
- name: Build Ghostty.app
run: |
cd macos
xcodebuild -target Ghostty -configuration Release
# We inject the "build number" as simply the number of commits since HEAD.
# This will be a monotonically always increasing build number that we use.
- name: Update Info.plist
env:
SPARKLE_KEY_PUB: ${{ secrets.PROD_MACOS_SPARKLE_KEY_PUB }}
run: |
# Version Info
/usr/libexec/PlistBuddy -c "Set :GhosttyCommit $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GHOSTTY_BUILD" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
# Updater
/usr/libexec/PlistBuddy -c "Set :SUPublicEDKey $SPARKLE_KEY_PUB" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Delete :SUEnableAutomaticChecks" "macos/build/Release/Ghostty.app/Contents/Info.plist"
- name: Codesign app bundle
env:
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
# Turn our base64-encoded certificate back to a regular .p12 file
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
# We need to create a new keychain, otherwise using the certificate will prompt
# with a UI dialog asking for the certificate password, which we can't
# use in a headless CI environment
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
# Codesign Sparkle. Some notes here:
# - The XPC services aren't used since we don't sandbox Ghostty,
# but since they're part of the build, they still need to be
# codesigned.
# - The binaries in the "Versions" folders need to NOT be symlinks.
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework"
# Codesign the app bundle
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime --entitlements "macos/Ghostty.entitlements" macos/build/Release/Ghostty.app
- name: Create DMG
env:
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
run: |
npm install --global create-dmg
create-dmg \
--identity="$MACOS_CERTIFICATE_NAME" \
./macos/build/Release/Ghostty.app \
./
mv ./Ghostty*.dmg ./Ghostty.dmg
- name: "Notarize DMG"
env:
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
# Store the notarization credentials so that we can prevent a UI password dialog
# from blocking the CI
echo "Create keychain profile"
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
# you're curious
echo "Notarize dmg"
xcrun notarytool submit "Ghostty.dmg" --keychain-profile "notarytool-profile" --wait
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available. We do this to
# both the app and the dmg
echo "Attach staple"
xcrun stapler staple "Ghostty.dmg"
xcrun stapler staple "macos/build/Release/Ghostty.app"
# Zip up the app and symbols
- name: Zip App
run: |
cd macos/build/Release
zip -9 -r --symlinks ../../../ghostty-macos-universal.zip Ghostty.app
zip -9 -r --symlinks ../../../ghostty-macos-universal-dsym.zip Ghostty.app.dSYM/
# Update Release
- name: Release
uses: softprops/action-gh-release@v2
with:
name: 'Ghostty Tip ("Nightly")'
prerelease: true
tag_name: tip
target_commitish: ${{ github.sha }}
files: |
ghostty-macos-universal.zip
Ghostty.dmg
token: ${{ secrets.GH_RELEASE_TOKEN }}
# Create our appcast for Sparkle
- name: Generate Appcast
env:
SPARKLE_KEY: ${{ secrets.PROD_MACOS_SPARKLE_KEY }}
run: |
echo $SPARKLE_KEY > signing.key
sign_update -f signing.key Ghostty.dmg > sign_update.txt
curl -L https://tip.files.ghostty.org/appcast.xml > appcast.xml
python3 ./dist/macos/update_appcast_tip.py
test -f appcast_new.xml
# Upload our binaries first
- name: Prep R2 Storage
run: |
mkdir blob
mkdir -p blob/${GHOSTTY_COMMIT_LONG}
cp ghostty-macos-universal.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal.zip
cp ghostty-macos-universal-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-dsym.zip
cp Ghostty.dmg blob/${GHOSTTY_COMMIT_LONG}/Ghostty.dmg
- name: Upload to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
r2-secret-access-key: ${{ secrets.CF_R2_TIP_SECRET_KEY }}
r2-bucket: ghostty-tip
source-dir: blob
destination-dir: ./
# Now upload our appcast. This ensures that the appcast never
# gets out of sync with the binaries.
- name: Prep R2 Storage for Appcast
run: |
rm -r blob
mkdir blob
cp appcast_new.xml blob/appcast.xml
- name: Upload Appcast to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
r2-secret-access-key: ${{ secrets.CF_R2_TIP_SECRET_KEY }}
r2-bucket: ghostty-tip
source-dir: blob
destination-dir: ./
build-macos-debug-slow:
if: |
${{
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.repository_owner == 'ghostty-org' &&
github.ref_name == 'main'
)
}}
runs-on: namespace-profile-ghostty-macos
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Important so that build number generation works
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.5.1
run: |
mkdir -p .action/sparkle
cd .action/sparkle
curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
unzip sparkle.zip
echo "$(pwd)/bin" >> $GITHUB_PATH
# Load Build Number
- name: Build Number
run: |
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)" >> $GITHUB_ENV
# GhosttyKit is the framework that is built from Zig for our native
# Mac app to access. Build this in release mode.
- name: Build GhosttyKit
run: nix develop -c zig build -Doptimize=Debug
# The native app is built with native XCode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
# Nix breaks xcodebuild so this has to be run outside.
- name: Build Ghostty.app
run: |
cd macos
xcodebuild -target Ghostty -configuration Release
# We inject the "build number" as simply the number of commits since HEAD.
# This will be a monotonically always increasing build number that we use.
- name: Update Info.plist
env:
SPARKLE_KEY_PUB: ${{ secrets.PROD_MACOS_SPARKLE_KEY_PUB }}
run: |
# Version Info
/usr/libexec/PlistBuddy -c "Set :GhosttyCommit $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GHOSTTY_BUILD" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
# Updater
/usr/libexec/PlistBuddy -c "Set :SUPublicEDKey $SPARKLE_KEY_PUB" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Delete :SUEnableAutomaticChecks" "macos/build/Release/Ghostty.app/Contents/Info.plist"
- name: Codesign app bundle
env:
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
# Turn our base64-encoded certificate back to a regular .p12 file
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
# We need to create a new keychain, otherwise using the certificate will prompt
# with a UI dialog asking for the certificate password, which we can't
# use in a headless CI environment
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
# Codesign Sparkle. Some notes here:
# - The XPC services aren't used since we don't sandbox Ghostty,
# but since they're part of the build, they still need to be
# codesigned.
# - The binaries in the "Versions" folders need to NOT be symlinks.
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework"
# Codesign the app bundle
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime --entitlements "macos/Ghostty.entitlements" macos/build/Release/Ghostty.app
- name: "Notarize app bundle"
env:
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
# Store the notarization credentials so that we can prevent a UI password dialog
# from blocking the CI
echo "Create keychain profile"
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
# We can't notarize an app bundle directly, but we need to compress it as an archive.
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
ditto -c -k --keepParent "macos/build/Release/Ghostty.app" "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
# you're curious
echo "Notarize app"
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
xcrun stapler staple "macos/build/Release/Ghostty.app"
# Zip up the app
- name: Zip App
run: |
cd macos/build/Release
zip -9 -r --symlinks ../../../ghostty-macos-universal-debug-slow.zip Ghostty.app
zip -9 -r --symlinks ../../../ghostty-macos-universal-debug-slow-dsym.zip Ghostty.app.dSYM/
# Update Release
- name: Release
uses: softprops/action-gh-release@v2
with:
name: 'Ghostty Tip ("Nightly")'
prerelease: true
tag_name: tip
target_commitish: ${{ github.sha }}
files: ghostty-macos-universal-debug-slow.zip
token: ${{ secrets.GH_RELEASE_TOKEN }}
# Update Blob Storage
- name: Prep R2 Storage
run: |
mkdir blob
mkdir -p blob/${GHOSTTY_COMMIT_LONG}
cp ghostty-macos-universal-debug-slow.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow.zip
cp ghostty-macos-universal-debug-slow-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-slow-dsym.zip
- name: Upload to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
r2-secret-access-key: ${{ secrets.CF_R2_TIP_SECRET_KEY }}
r2-bucket: ghostty-tip
source-dir: blob
destination-dir: ./
build-macos-debug-fast:
if: |
${{
github.event_name == 'workflow_dispatch' ||
(
github.event.workflow_run.conclusion == 'success' &&
github.repository_owner == 'ghostty-org' &&
github.ref_name == 'main'
)
}}
runs-on: namespace-profile-ghostty-macos
timeout-minutes: 90
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
# Important so that build number generation works
fetch-depth: 0
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v30
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v15
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: XCode Select
run: sudo xcode-select -s /Applications/Xcode_16.0.app
# Setup Sparkle
- name: Setup Sparkle
env:
SPARKLE_VERSION: 2.5.1
run: |
mkdir -p .action/sparkle
cd .action/sparkle
curl -L https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-for-Swift-Package-Manager.zip > sparkle.zip
unzip sparkle.zip
echo "$(pwd)/bin" >> $GITHUB_PATH
# Load Build Number
- name: Build Number
run: |
echo "GHOSTTY_BUILD=$(git rev-list --count head)" >> $GITHUB_ENV
echo "GHOSTTY_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "GHOSTTY_COMMIT_LONG=$(git rev-parse HEAD)" >> $GITHUB_ENV
# GhosttyKit is the framework that is built from Zig for our native
# Mac app to access. Build this in release mode.
- name: Build GhosttyKit
run: nix develop -c zig build -Doptimize=ReleaseSafe
# The native app is built with native XCode tooling. This also does
# codesigning. IMPORTANT: this must NOT run in a Nix environment.
# Nix breaks xcodebuild so this has to be run outside.
- name: Build Ghostty.app
run: |
cd macos
xcodebuild -target Ghostty -configuration Release
# We inject the "build number" as simply the number of commits since HEAD.
# This will be a monotonically always increasing build number that we use.
- name: Update Info.plist
env:
SPARKLE_KEY_PUB: ${{ secrets.PROD_MACOS_SPARKLE_KEY_PUB }}
run: |
# Version Info
/usr/libexec/PlistBuddy -c "Set :GhosttyCommit $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $GHOSTTY_BUILD" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $GHOSTTY_COMMIT" "macos/build/Release/Ghostty.app/Contents/Info.plist"
# Updater
/usr/libexec/PlistBuddy -c "Set :SUPublicEDKey $SPARKLE_KEY_PUB" "macos/build/Release/Ghostty.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Delete :SUEnableAutomaticChecks" "macos/build/Release/Ghostty.app/Contents/Info.plist"
- name: Codesign app bundle
env:
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
run: |
# Turn our base64-encoded certificate back to a regular .p12 file
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
# We need to create a new keychain, otherwise using the certificate will prompt
# with a UI dialog asking for the certificate password, which we can't
# use in a headless CI environment
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain
security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain
# Codesign Sparkle. Some notes here:
# - The XPC services aren't used since we don't sandbox Ghostty,
# but since they're part of the build, they still need to be
# codesigned.
# - The binaries in the "Versions" folders need to NOT be symlinks.
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Downloader.xpc"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework/Versions/B/Updater.app"
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime "macos/build/Release/Ghostty.app/Contents/Frameworks/Sparkle.framework"
# Codesign the app bundle
/usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime --entitlements "macos/Ghostty.entitlements" macos/build/Release/Ghostty.app
- name: "Notarize app bundle"
env:
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
run: |
# Store the notarization credentials so that we can prevent a UI password dialog
# from blocking the CI
echo "Create keychain profile"
xcrun notarytool store-credentials "notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
# We can't notarize an app bundle directly, but we need to compress it as an archive.
# Therefore, we create a zip file containing our app bundle, so that we can send it to the
# notarization service
echo "Creating temp notarization archive"
ditto -c -k --keepParent "macos/build/Release/Ghostty.app" "notarization.zip"
# Here we send the notarization request to the Apple's Notarization service, waiting for the result.
# This typically takes a few seconds inside a CI environment, but it might take more depending on the App
# characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if
# you're curious
echo "Notarize app"
xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait
# Finally, we need to "attach the staple" to our executable, which will allow our app to be
# validated by macOS even when an internet connection is not available.
echo "Attach staple"
xcrun stapler staple "macos/build/Release/Ghostty.app"
# Zip up the app
- name: Zip App
run: |
cd macos/build/Release
zip -9 -r --symlinks ../../../ghostty-macos-universal-debug-fast.zip Ghostty.app
zip -9 -r --symlinks ../../../ghostty-macos-universal-debug-fast-dsym.zip Ghostty.app.dSYM/
# Update Release
- name: Release
uses: softprops/action-gh-release@v2
with:
name: 'Ghostty Tip ("Nightly")'
prerelease: true
tag_name: tip
target_commitish: ${{ github.sha }}
files: ghostty-macos-universal-debug-fast.zip
token: ${{ secrets.GH_RELEASE_TOKEN }}
# Update Blob Storage
- name: Prep R2 Storage
run: |
mkdir blob
mkdir -p blob/${GHOSTTY_COMMIT_LONG}
cp ghostty-macos-universal-debug-fast.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast.zip
cp ghostty-macos-universal-debug-fast-dsym.zip blob/${GHOSTTY_COMMIT_LONG}/ghostty-macos-universal-debug-fast-dsym.zip
- name: Upload to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_R2_TIP_AWS_KEY }}
r2-secret-access-key: ${{ secrets.CF_R2_TIP_SECRET_KEY }}
r2-bucket: ghostty-tip
source-dir: blob
destination-dir: ./