on: workflow_run: workflows: [Test] types: [completed] branches: [main] workflow_dispatch: {} name: Release Tip 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 "<>" git tag -fa tip -m "Latest Continuous Release" ${GITHUB_SHA} git push --force origin tip build-macos: if: | ${{ github.event_name == 'workflow_dispatch' || ( github.event.workflow_run.conclusion == 'success' && github.repository_owner == 'mitchellh' && 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@v26 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v14 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" # 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 # 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 -Dstatic=true -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" - 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.zip Ghostty.app # 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 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-macos-universal.zip > sign_update.txt curl -L https://tip.files.ghostty.dev/appcast.xml > appcast.xml python3 ./dist/macos/update_appcast_tip.py test -f appcast_new.xml # Update Blob Storage - name: Prep R2 Storage run: | mkdir blob mkdir -p blob/${GHOSTTY_BUILD} cp ghostty-macos-universal.zip blob/${GHOSTTY_BUILD}/ghostty-macos-universal.zip cp appcast_new.xml blob/appcast.xml - 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: if: | ${{ github.event_name == 'workflow_dispatch' || ( github.event.workflow_run.conclusion == 'success' && github.repository_owner == 'mitchellh' && 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@v26 with: nix_path: nixpkgs=channel:nixos-unstable - uses: cachix/cachix-action@v14 with: name: ghostty authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" # 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 # 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 -Dstatic=true -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" - 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.zip Ghostty.app # 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.zip token: ${{ secrets.GH_RELEASE_TOKEN }} # Update Blob Storage - name: Prep R2 Storage run: | mkdir blob mkdir -p blob/${GHOSTTY_BUILD} cp ghostty-macos-universal-debug.zip blob/${GHOSTTY_BUILD}/ghostty-macos-universal-debug.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: ./