From caf6ebc6efd5e007c62232d5e187027d21940f26 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 13:36:18 -0800 Subject: [PATCH 01/13] macos: add Sparkle via Swift PM --- macos/Ghostty.xcodeproj/project.pbxproj | 31 ++++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 14 +++++++++ macos/GhosttyDebug.entitlements | 22 +++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 macos/Ghostty.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 macos/GhosttyDebug.entitlements diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 87033b409..fbd5c64b0 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ A51BFC1E2B2FB5CE00E92F16 /* About.xib in Resources */ = {isa = PBXBuildFile; fileRef = A51BFC1D2B2FB5CE00E92F16 /* About.xib */; }; A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */; }; A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC212B2FB6B400E92F16 /* AboutView.swift */; }; + A51BFC272B30F1B800E92F16 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A51BFC262B30F1B800E92F16 /* Sparkle */; }; A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; }; A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; }; A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; }; @@ -57,6 +58,7 @@ A51BFC1D2B2FB5CE00E92F16 /* About.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = About.xib; sourceTree = ""; }; A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = ""; }; A51BFC212B2FB6B400E92F16 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; + A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyDebug.entitlements; sourceTree = ""; }; A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = ""; }; A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; @@ -102,6 +104,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A51BFC272B30F1B800E92F16 /* Sparkle in Frameworks */, A56B880B2A840447007A0E29 /* Carbon.framework in Frameworks */, A571AB1D2A206FCF00248498 /* GhosttyKit.xcframework in Frameworks */, ); @@ -221,6 +224,7 @@ A571AB1C2A206FC600248498 /* Ghostty-Info.plist */, A5B30538299BEAAB0047F10C /* Assets.xcassets */, A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */, + A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */, A54CD6ED299BEB14008C95BB /* Sources */, A5D495A3299BECBA00DD1313 /* Frameworks */, A5A1F8862A489D7400D1E8BC /* Resources */, @@ -280,6 +284,9 @@ dependencies = ( ); name = Ghostty; + packageProductDependencies = ( + A51BFC262B30F1B800E92F16 /* Sparkle */, + ); productName = Ghostty; productReference = A5B30531299BEAAA0047F10C /* Ghostty.app */; productType = "com.apple.product-type.application"; @@ -308,6 +315,9 @@ Base, ); mainGroup = A5B30528299BEAAA0047F10C; + packageReferences = ( + A51BFC252B30F1B700E92F16 /* XCRemoteSwiftPackageReference "Sparkle" */, + ); productRefGroup = A5B30532299BEAAA0047F10C /* Products */; projectDirPath = ""; projectRoot = ""; @@ -496,7 +506,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; - CODE_SIGN_ENTITLEMENTS = Ghostty.entitlements; + CODE_SIGN_ENTITLEMENTS = GhosttyDebug.entitlements; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -583,6 +593,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + A51BFC252B30F1B700E92F16 /* XCRemoteSwiftPackageReference "Sparkle" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparkle-project/Sparkle"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.5.1; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + A51BFC262B30F1B800E92F16 /* Sparkle */ = { + isa = XCSwiftPackageProductDependency; + package = A51BFC252B30F1B700E92F16 /* XCRemoteSwiftPackageReference "Sparkle" */; + productName = Sparkle; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = A5B30529299BEAAA0047F10C /* Project object */; } diff --git a/macos/Ghostty.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/macos/Ghostty.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..70af83f79 --- /dev/null +++ b/macos/Ghostty.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "sparkle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparkle-project/Sparkle", + "state" : { + "revision" : "1f07f4096e52f19b5e7abaa697b7fc592b7ff57c", + "version" : "2.5.1" + } + } + ], + "version" : 2 +} diff --git a/macos/GhosttyDebug.entitlements b/macos/GhosttyDebug.entitlements new file mode 100644 index 000000000..12b429c28 --- /dev/null +++ b/macos/GhosttyDebug.entitlements @@ -0,0 +1,22 @@ + + + + + com.apple.security.automation.apple-events + + com.apple.security.cs.disable-library-validation + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.personal-information.addressbook + + com.apple.security.personal-information.calendars + + com.apple.security.personal-information.location + + com.apple.security.personal-information.photos-library + + + From f192ffd5bc644227d723469aaf4712f8d8317fd5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 13:41:46 -0800 Subject: [PATCH 02/13] macos: boilerplate to setup updater menu item --- macos/Sources/AppDelegate.swift | 17 ++++++++++++++++- macos/Sources/MainMenu.xib | 4 ++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index d1b16d176..e39b34365 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -1,6 +1,7 @@ import AppKit import UserNotifications import OSLog +import Sparkle import GhosttyKit class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, GhosttyAppStateDelegate { @@ -12,6 +13,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti ) /// Various menu items so that we can programmatically sync the keyboard shortcut with the Ghostty config. + @IBOutlet private var menuCheckForUpdates: NSMenuItem? @IBOutlet private var menuOpenConfig: NSMenuItem? @IBOutlet private var menuReloadConfig: NSMenuItem? @IBOutlet private var menuQuit: NSMenuItem? @@ -56,8 +58,17 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti /// Manages our terminal windows. let terminalManager: TerminalManager + /// Manages updates + let updaterController: SPUStandardUpdaterController + override init() { - self.terminalManager = TerminalManager(ghostty) + terminalManager = TerminalManager(ghostty) + updaterController = SPUStandardUpdaterController( + startingUpdater: true, + updaterDelegate: nil, + userDriverDelegate: nil + ) + super.init() ghostty.delegate = self @@ -80,6 +91,10 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti "ApplePressAndHoldEnabled": false, ]) + // Hook up updater menu + menuCheckForUpdates?.target = updaterController + menuCheckForUpdates?.action = #selector(SPUStandardUpdaterController.checkForUpdates(_:)) + // Let's launch our first window. We only do this if we have no other windows. It // is possible to have other windows if we're opening a URL since `application(_:openFile:)` // is called before this. diff --git a/macos/Sources/MainMenu.xib b/macos/Sources/MainMenu.xib index df7635f9a..87b197e6b 100644 --- a/macos/Sources/MainMenu.xib +++ b/macos/Sources/MainMenu.xib @@ -14,6 +14,7 @@ + @@ -58,6 +59,9 @@ + + + From 25342f545bbe90fb41bae4f426eb762797c47c7a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 13:56:32 -0800 Subject: [PATCH 03/13] macos: add update delegate so we can configure some behavior --- macos/Ghostty.xcodeproj/project.pbxproj | 12 ++++++++++++ macos/Sources/AppDelegate.swift | 12 +++++++++--- macos/Sources/Features/Update/UpdateDelegate.swift | 8 ++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 macos/Sources/Features/Update/UpdateDelegate.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index fbd5c64b0..e43e98993 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ A51BFC202B2FB64F00E92F16 /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */; }; A51BFC222B2FB6B400E92F16 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC212B2FB6B400E92F16 /* AboutView.swift */; }; A51BFC272B30F1B800E92F16 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A51BFC262B30F1B800E92F16 /* Sparkle */; }; + A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */; }; A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */; }; A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; }; A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; }; @@ -59,6 +60,7 @@ A51BFC1F2B2FB64F00E92F16 /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = ""; }; A51BFC212B2FB6B400E92F16 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; A51BFC282B30F26D00E92F16 /* GhosttyDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = GhosttyDebug.entitlements; sourceTree = ""; }; + A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateDelegate.swift; sourceTree = ""; }; A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Input.swift; sourceTree = ""; }; A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; @@ -123,6 +125,14 @@ path = About; sourceTree = ""; }; + A51BFC292B30F69F00E92F16 /* Update */ = { + isa = PBXGroup; + children = ( + A51BFC2A2B30F6BE00E92F16 /* UpdateDelegate.swift */, + ); + path = Update; + sourceTree = ""; + }; A53426362A7DC53000EBB7A2 /* Features */ = { isa = PBXGroup; children = ( @@ -131,6 +141,7 @@ A5E112912AF73E4D00C6E0C2 /* ClipboardConfirmation */, A534263E2A7DCC5800EBB7A2 /* Settings */, A51BFC1C2B2FB5AB00E92F16 /* About */, + A51BFC292B30F69F00E92F16 /* Update */, ); path = Features; sourceTree = ""; @@ -359,6 +370,7 @@ A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */, A59630972AEE163600D64628 /* HostingWindow.swift in Sources */, A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */, + A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */, A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */, A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */, A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index e39b34365..41c6ea5de 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -4,7 +4,12 @@ import OSLog import Sparkle import GhosttyKit -class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, GhosttyAppStateDelegate { +class AppDelegate: NSObject, + ObservableObject, + NSApplicationDelegate, + UNUserNotificationCenterDelegate, + GhosttyAppStateDelegate +{ // The application logger. We should probably move this at some point to a dedicated // class/struct but for now it lives here! 🤷‍♂️ static let logger = Logger( @@ -60,15 +65,16 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, UNUserNoti /// Manages updates let updaterController: SPUStandardUpdaterController + let updaterDelegate: UpdaterDelegate = UpdaterDelegate() override init() { terminalManager = TerminalManager(ghostty) updaterController = SPUStandardUpdaterController( startingUpdater: true, - updaterDelegate: nil, + updaterDelegate: updaterDelegate, userDriverDelegate: nil ) - + super.init() ghostty.delegate = self diff --git a/macos/Sources/Features/Update/UpdateDelegate.swift b/macos/Sources/Features/Update/UpdateDelegate.swift new file mode 100644 index 000000000..ab58b0021 --- /dev/null +++ b/macos/Sources/Features/Update/UpdateDelegate.swift @@ -0,0 +1,8 @@ +import Sparkle + +class UpdaterDelegate: NSObject, SPUUpdaterDelegate { + func feedURLString(for updater: SPUUpdater) -> String? { + // TODO + return nil + } +} From 51fa5ea3d4c858c73560ac5c990bbffd348ae3b7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 20:14:17 -0800 Subject: [PATCH 04/13] ci: setup sparkle binaries in release --- .github/workflows/release-tip.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index fb76f2ba0..4537f959d 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -48,6 +48,17 @@ jobs: with: nix_path: nixpkgs=channel:nixos-unstable + # 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 + # Setup our S3 client - name: Setup s3cmd uses: s3-actions/s3cmd@v1.5.0 @@ -55,7 +66,7 @@ jobs: provider: cloudflare account_id: ${{ secrets.CF_R2_TIP_ACCOUNT_ID }} access_key: ${{ secrets.CF_R2_TIP_AWS_KEY }} - secreT_key: ${{ secrets.CF_R2_TIP_SECRET_KEY }} + secret_key: ${{ secrets.CF_R2_TIP_SECRET_KEY }} # Load Build Number - name: Build Number @@ -76,12 +87,18 @@ jobs: # 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: Inject Build Number + - 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: Zip Unsigned App run: nix develop -c sh -c 'cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal-unsigned.zip Ghostty.app' From c352d88afd19fd9fecb71f95fdb62aa1213c8bd5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 21:15:49 -0800 Subject: [PATCH 05/13] ci: generate appcast, upload it --- .github/workflows/release-tip.yml | 12 ++++++ dist/macos/update_appcast_tip.py | 66 +++++++++++++++++++++++++++++++ macos/Ghostty-Info.plist | 2 + 3 files changed, 80 insertions(+) create mode 100644 dist/macos/update_appcast_tip.py diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 4537f959d..291f6659e 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -179,7 +179,19 @@ jobs: 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 src/App.zig > 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: Upload to Blob Storage run: | s3cmd put ghostty-macos-universal.zip s3://ghostty-tip/${GHOSTTY_BUILD}/ghostty-macos-universal.zip + s3cmd put appcast_new.xml s3://ghostty-tip/appcast.xml diff --git a/dist/macos/update_appcast_tip.py b/dist/macos/update_appcast_tip.py new file mode 100644 index 000000000..ec3878bc1 --- /dev/null +++ b/dist/macos/update_appcast_tip.py @@ -0,0 +1,66 @@ +""" +This script is used to update the appcast.xml file for Ghostty releases. +The script is currently hardcoded to only work for tip releases and therefore +doesn't have rich release notes, hardcodes the URL to the tip bucket, etc. + +This expects the following files in the current directory: + - sign_update.txt - contains the output from "sign_update" in the Sparkle + framework for the current build. + - appcast.xml - the existing appcast file. + +And the following environment variables to be set: + - GHOSTTY_BUILD - the build number + - GHOSTTY_COMMIT - the commit hash + +The script will output a new appcast file called appcast_new.xml. +""" + +import os +import xml.etree.ElementTree as ET +from datetime import datetime, timezone + +now = datetime.now(timezone.utc) +build = os.environ["GHOSTTY_BUILD"] +commit = os.environ["GHOSTTY_COMMIT"] + +# Read our sign_update output +with open("sign_update.txt", "r") as f: + # format is a=b b=c etc. create a map of this. values may contain equal + # signs, so we can't just split on equal signs. + attrs = {} + for pair in f.read().split(" "): + key, value = pair.split("=", 1) + value = value.strip() + if value[0] == '"': + value = value[1:-1] + attrs[key] = value + +# Open our existing appcast and find the channel element. This is where +# we'll add our new item. +et = ET.parse('appcast.xml') +channel = et.find("channel") + +# Create the item using some absoultely terrible XML manipulation. +item = ET.SubElement(channel, "item") +elem = ET.SubElement(item, "title") +elem.text = f"Build {build}" +elem = ET.SubElement(item, "pubDate") +elem.text = now.strftime("%a, %d %b %Y %H:%M:%S %z") +elem = ET.SubElement(item, "sparkle:version") +elem.text = build +elem = ET.SubElement(item, "sparkle:shortVersionString") +elem.text = commit +elem = ET.SubElement(item, "sparkle:minimumSystemVersion") +elem.text = "12.0.0" +elem = ET.SubElement(item, "description") +elem.text = f""" +

Automated build from commit

{commit}
.

+""" +elem = ET.SubElement(item, "enclosure") +elem.set("url", f"https://tip.files.ghostty.dev/{build}/ghostty-macos-universal.zip") +elem.set("type", "application/octet-stream") +for key, value in attrs.items(): + elem.set(key, value) + +# Output the new appcast. +et.write("appcast_new.xml") diff --git a/macos/Ghostty-Info.plist b/macos/Ghostty-Info.plist index f0311e217..a2066680c 100644 --- a/macos/Ghostty-Info.plist +++ b/macos/Ghostty-Info.plist @@ -91,6 +91,8 @@ A program in Ghostty wants to use speech recognition. NSSystemAdministrationUsageDescription A program in Ghostty requires elevated privileges. + SUPublicEDKey + wsNcGf5hirwtdXMVnYoxRIX/SqZQLMOsYlD3q3imeok= CFBundleVersion CFBundleShortVersionString From 3baa6213b0e5d62770d86f93664471bcf67d34e8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 21:25:26 -0800 Subject: [PATCH 06/13] ci: disable releases temporarily --- .github/workflows/release-tip.yml | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 291f6659e..5857077d8 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -103,15 +103,15 @@ jobs: run: nix develop -c sh -c 'cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal-unsigned.zip Ghostty.app' # Update Release - - name: Release Unsigned - uses: softprops/action-gh-release@v1 - with: - name: 'Ghostty Tip ("Nightly")' - prerelease: true - tag_name: tip - target_commitish: ${{ github.sha }} - files: ghostty-macos-universal-unsigned.zip - token: ${{ secrets.GH_RELEASE_TOKEN }} + # - name: Release Unsigned + # uses: softprops/action-gh-release@v1 + # with: + # name: 'Ghostty Tip ("Nightly")' + # prerelease: true + # tag_name: tip + # target_commitish: ${{ github.sha }} + # files: ghostty-macos-universal-unsigned.zip + # token: ${{ secrets.GH_RELEASE_TOKEN }} - name: Codesign app bundle env: @@ -169,15 +169,15 @@ jobs: run: cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal.zip Ghostty.app # Update Release - - name: Release - uses: softprops/action-gh-release@v1 - with: - name: 'Ghostty Tip ("Nightly")' - prerelease: true - tag_name: tip - target_commitish: ${{ github.sha }} - files: ghostty-macos-universal.zip - token: ${{ secrets.GH_RELEASE_TOKEN }} + # - name: Release + # uses: softprops/action-gh-release@v1 + # 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 From b5aaad9d34359e1b4f37df52dd6b82cdf98086ad Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 21:32:22 -0800 Subject: [PATCH 07/13] ci: correct filename for appcast --- .github/workflows/release-tip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 5857077d8..787057eb3 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -185,7 +185,7 @@ jobs: SPARKLE_KEY: ${{ secrets.PROD_MACOS_SPARKLE_KEY }} run: | echo $SPARKLE_KEY > signing.key - sign_update -f signing.key src/App.zig > sign_update.txt + 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 From 79416cc0a7d7d4e050e4bffdcf152f4146900dcc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 18 Dec 2023 22:13:48 -0800 Subject: [PATCH 08/13] ci: codesign nested frameworks --- .github/workflows/release-tip.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index 787057eb3..ea2babb81 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -132,8 +132,14 @@ jobs: 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 - # We finally codesign our app bundle, specifying the Hardened runtime option - /usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime macos/build/Release/Ghostty.app -v + # Codesign Sparkle + /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 --deep -f -s "$MACOS_CERTIFICATE_NAME" -o runtime macos/build/Release/Ghostty.app - name: "Notarize app bundle" env: From 0ccdcd3f0f568269ad5bc622fb14404694e3a3c0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 19 Dec 2023 07:44:07 -0800 Subject: [PATCH 09/13] dist/macos: appcast needs namespace, better formatting --- dist/macos/update_appcast_tip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dist/macos/update_appcast_tip.py b/dist/macos/update_appcast_tip.py index ec3878bc1..fae10c627 100644 --- a/dist/macos/update_appcast_tip.py +++ b/dist/macos/update_appcast_tip.py @@ -35,6 +35,9 @@ with open("sign_update.txt", "r") as f: value = value[1:-1] attrs[key] = value +# We need to register our namespaces before reading or writing any files. +ET.register_namespace("sparkle", "http://www.andymatuschak.org/xml-namespaces/sparkle") + # Open our existing appcast and find the channel element. This is where # we'll add our new item. et = ET.parse('appcast.xml') @@ -54,7 +57,7 @@ elem = ET.SubElement(item, "sparkle:minimumSystemVersion") elem.text = "12.0.0" elem = ET.SubElement(item, "description") elem.text = f""" -

Automated build from commit

{commit}
.

+

Automated build from commit {commit}.

""" elem = ET.SubElement(item, "enclosure") elem.set("url", f"https://tip.files.ghostty.dev/{build}/ghostty-macos-universal.zip") @@ -63,4 +66,4 @@ for key, value in attrs.items(): elem.set(key, value) # Output the new appcast. -et.write("appcast_new.xml") +et.write("appcast_new.xml", xml_declaration=True, encoding="utf-8") From 8218c96cc64db7b896085de15138c949d2436e84 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 19 Dec 2023 07:45:52 -0800 Subject: [PATCH 10/13] ci: manually codesign since --deep is deprecated --- .github/workflows/release-tip.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index ea2babb81..f58d6ae05 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -132,14 +132,19 @@ jobs: 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 + # 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 --deep -f -s "$MACOS_CERTIFICATE_NAME" -o runtime macos/build/Release/Ghostty.app + /usr/bin/codesign --verbose -f -s "$MACOS_CERTIFICATE_NAME" -o runtime macos/build/Release/Ghostty.app - name: "Notarize app bundle" env: From 0acf7837006c11c35acf27c404c84b94a86a7de1 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 19 Dec 2023 08:04:36 -0800 Subject: [PATCH 11/13] macos: set the update URL --- macos/Sources/Features/Update/UpdateDelegate.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/macos/Sources/Features/Update/UpdateDelegate.swift b/macos/Sources/Features/Update/UpdateDelegate.swift index ab58b0021..c116432df 100644 --- a/macos/Sources/Features/Update/UpdateDelegate.swift +++ b/macos/Sources/Features/Update/UpdateDelegate.swift @@ -2,7 +2,10 @@ import Sparkle class UpdaterDelegate: NSObject, SPUUpdaterDelegate { func feedURLString(for updater: SPUUpdater) -> String? { - // TODO - return nil + // Eventually w want to support multiple channels. Sparkle itself supports + // channels but we probably don't want some appcasts in the same file (i.e. + // tip) so this would be the place to change that. For now, we hardcode the + // tip appcast URL since it is all we support. + return "https://tip.files.ghostty.dev/appcast.xml" } } From 8c7453162089415114a860a404d7caa5234c0f2d Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 19 Dec 2023 09:36:44 -0800 Subject: [PATCH 12/13] macos: update the release notes with link to GH project --- dist/macos/update_appcast_tip.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dist/macos/update_appcast_tip.py b/dist/macos/update_appcast_tip.py index fae10c627..200583e8a 100644 --- a/dist/macos/update_appcast_tip.py +++ b/dist/macos/update_appcast_tip.py @@ -58,6 +58,12 @@ elem.text = "12.0.0" elem = ET.SubElement(item, "description") elem.text = f"""

Automated build from commit {commit}.

+

+These are automatic per-commit builds generated from the main Git branch. +We do not generate any release notes for these builds. You can view the full +commit history +on GitHub for all changes. +

""" elem = ET.SubElement(item, "enclosure") elem.set("url", f"https://tip.files.ghostty.dev/{build}/ghostty-macos-universal.zip") From d1b90e1689bce6d00dd4e23d743b528ccb1bbeac Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 19 Dec 2023 09:38:02 -0800 Subject: [PATCH 13/13] ci: uncomment the release-to-github code --- .github/workflows/release-tip.yml | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index f58d6ae05..590fd8a5a 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -103,15 +103,15 @@ jobs: run: nix develop -c sh -c 'cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal-unsigned.zip Ghostty.app' # Update Release - # - name: Release Unsigned - # uses: softprops/action-gh-release@v1 - # with: - # name: 'Ghostty Tip ("Nightly")' - # prerelease: true - # tag_name: tip - # target_commitish: ${{ github.sha }} - # files: ghostty-macos-universal-unsigned.zip - # token: ${{ secrets.GH_RELEASE_TOKEN }} + - name: Release Unsigned + uses: softprops/action-gh-release@v1 + with: + name: 'Ghostty Tip ("Nightly")' + prerelease: true + tag_name: tip + target_commitish: ${{ github.sha }} + files: ghostty-macos-universal-unsigned.zip + token: ${{ secrets.GH_RELEASE_TOKEN }} - name: Codesign app bundle env: @@ -180,15 +180,15 @@ jobs: run: cd macos/build/Release && zip -9 -r --symlinks ../../../ghostty-macos-universal.zip Ghostty.app # Update Release - # - name: Release - # uses: softprops/action-gh-release@v1 - # with: - # name: 'Ghostty Tip ("Nightly")' - # prerelease: true - # tag_name: tip - # target_commitish: ${{ github.sha }} - # files: ghostty-macos-universal.zip - # token: ${{ secrets.GH_RELEASE_TOKEN }} + - name: Release + uses: softprops/action-gh-release@v1 + 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