Compare commits

...

91 Commits

Author SHA1 Message Date
Micha 55a266014c chore: release 26.0.68 2026-01-03 13:52:34 +01:00
Micha c002cab616 chore: disable sandbox for now to use Sparkle updates
Disable app-sandbox to allow Sparkle auto-updates to work properly.
Keep entitlements file structure for future sandbox re-enablement.
Sandbox integration with Sparkle requires more complex authorization
setup that can be tackled later when preparing for App Store.
2026-01-03 13:50:26 +01:00
Micha 117134cead chore: release 26.0.67 2026-01-03 13:36:30 +01:00
Micha 35711d33c0 Sparkle test 2026-01-03 13:34:50 +01:00
Micha 4f3d56dc3c chore: release 26.0.66 2026-01-03 13:28:45 +01:00
Micha 0d80a0f912 fix: use basic Sparkle updater for sandboxed apps
Disable SUEnableInstallerLauncherService and remove XPC entitlements.
Use Sparkle's standard update mechanism which works with sandboxed apps.
Add file access entitlements for update storage.
2026-01-03 13:26:37 +01:00
Micha b1d6e61f05 chore: release 26.0.65 2025-12-30 20:19:29 +01:00
Micha 2dd2c2154f fix: sign Sparkle framework separately for sandboxed builds
Sign the Sparkle framework before signing the whole app to ensure
proper code signature chain for sandboxed installation.
2025-12-30 20:17:47 +01:00
Micha c6ecbbe511 chore: release 26.0.64 2025-12-30 20:14:03 +01:00
Micha 77a145604c Sparkle test 2025-12-30 20:12:24 +01:00
Micha 0016030ff3 chore: release 26.0.63 2025-12-30 20:06:55 +01:00
Micha 615d664731 fix: configure sandbox for Sparkle installer with proper entitlements
- Add downloads folder read-write access for installer
- Enable SUEnableInstallerLauncherService for sandboxed update installation
- Keep XPC service entitlements for installer communication
2025-12-30 20:04:26 +01:00
Micha 5644fbdfe0 chore: release 26.0.62 2025-12-30 19:55:55 +01:00
Micha 9b5883fe77 Sparkle test 2025-12-30 19:54:08 +01:00
Micha 5d15810802 chore: release 26.0.61 2025-12-30 19:53:12 +01:00
Micha 519d15ed10 Sparkle test 2025-12-30 19:51:35 +01:00
Micha 446bbe7f98 chore: release 26.0.60 2025-12-30 19:50:43 +01:00
Micha b67fffd3f0 fix: re-add XPC service entitlements for sandboxed Sparkle installer
Add back InstallerConnection and InstallerStatus entitlements which are
required for the sandboxed app to communicate with Sparkle's installer
XPC service.
2025-12-30 19:48:44 +01:00
Micha 84935ee8fd chore: release 26.0.59 2025-12-30 19:42:29 +01:00
Micha 0f266f7046 Sparkle test 2025-12-30 19:40:37 +01:00
Micha bff7c44c29 chore: release 26.0.58 2025-12-30 19:36:29 +01:00
Micha f930e8334f chore: remove configuration note from updates preferences
Remove the explanation text about configuring appcast URL and EdDSA key.
This is configuration for developers, not end users.
2025-12-30 19:33:14 +01:00
Micha 0032ad9b57 chore: remove update button from main toolbar
Remove the 'Check for Updates' button from the main window toolbar.
Updates are available via Preferences → Updates, which is sufficient.
2025-12-30 19:29:01 +01:00
Micha b7f5d1a762 chore: remove update logs UI from preferences
Remove the Show/Hide Logs button and logs view from the Updates preferences
tab. Keep the logging infrastructure in SparkleUpdater for diagnostics,
but don't display it in the UI.
2025-12-30 19:27:06 +01:00
Micha 5dc5621871 docs: update changelog with Sparkle updater fixes and improvements
Document the key fixes that made Sparkle updates work:
- Using ditto instead of zip to preserve code signatures
- XPC service entitlements for sandboxed apps
- In-app logging for update debugging
- Re-enabled sandbox with minimal entitlements
2025-12-30 19:23:36 +01:00
Micha 2a848c3251 chore: release 26.0.57 2025-12-30 19:20:11 +01:00
Micha bb4f972d58 feat: re-enable sandbox with minimal entitlements
- Re-enable app-sandbox
- Add network.client entitlement (required for Sparkle updates)
- Keep build script passing entitlements to codesign
- Use ditto for ZIP to preserve code signatures

This is a minimal sandbox configuration focused on security while
keeping updates working.
2025-12-30 19:14:40 +01:00
Micha 62d4a9ac96 chore: release 26.0.56 2025-12-30 19:09:35 +01:00
Micha 75fe670779 fix: use ditto instead of zip to preserve code signatures
zip breaks code signatures on macOS. Use ditto -c -k to create the update
ZIP archive while preserving the embedded code signature of the app bundle.
2025-12-30 19:07:10 +01:00
Micha a961baab28 chore: release 26.0.55 2025-12-30 18:59:42 +01:00
Micha 7246b132f6 Sparkle test 2025-12-30 18:58:04 +01:00
Micha 1afce31641 chore: release 26.0.54 2025-12-30 18:52:40 +01:00
Micha 281016bfc9 fix: remove entitlements from code signing for non-sandboxed app
For non-sandboxed apps, don't pass --entitlements to codesign.
This was causing code signature issues.
2025-12-30 18:50:41 +01:00
Micha 144ad27aa6 chore: release 26.0.53 2025-12-30 18:43:27 +01:00
Micha aa655bb7d6 Sparkle test 2025-12-30 18:41:33 +01:00
Micha 1ac34e1f04 chore: release 26.0.52 2025-12-30 18:33:44 +01:00
Micha 5f045c113a chore: simplify to non-sandboxed app with no entitlements
Remove all sandbox and XPC entitlements to test if Sparkle works without them.
2025-12-30 18:31:17 +01:00
Micha 2dbe739c97 chore: release 26.0.51 2025-12-30 18:24:24 +01:00
Micha da9dd2f7ed Sparkle test 2025-12-30 18:22:30 +01:00
Micha 2ae67b6675 chore: release 26.0.50 2025-12-30 18:10:29 +01:00
Micha 989717539c Sparkle test 2025-12-30 18:08:49 +01:00
Micha 6d05419abb chore: release 26.0.49 2025-12-30 18:03:29 +01:00
Micha be37bf526a Sparkle test 2025-12-30 18:01:57 +01:00
Micha 6c1f5c6d25 chore: release 26.0.48 2025-12-30 17:20:31 +01:00
Micha 32f97ff7d4 Sparkle test 2025-12-30 17:18:45 +01:00
Micha dbbe1752d1 fix: disable InstallerLauncherService for sandboxed app
For sandboxed apps, use standard Sparkle updater instead of the
InstallerLauncherService. Also add Downloader XPC service identifiers.
2025-12-30 16:40:27 +01:00
Micha 2fe9821ac1 chore: release 26.0.47 2025-12-30 16:07:14 +01:00
Micha 87d4bffb99 Sparkle test 2025-12-30 16:05:16 +01:00
Micha 92782716fc chore: release 26.0.46 2025-12-30 16:00:25 +01:00
Micha 002c9e8cf2 Sparkle test 2025-12-30 15:58:45 +01:00
Micha 8820244589 chore: release 26.0.45 2025-12-30 15:51:46 +01:00
Micha 48d2f0ea42 Sparkle test 2025-12-30 15:49:57 +01:00
Micha 1947d05d78 fix: specify exact Sparkle XPC service identifiers
Change XPC entitlements from boolean true to arrays with specific
service identifiers for InstallerConnection and InstallerStatus.
2025-12-30 15:45:10 +01:00
Micha 86039cd5a9 chore: release 26.0.44 2025-12-30 15:35:40 +01:00
Micha 4f9c008498 Sparkle test 2025-12-30 15:33:58 +01:00
Micha 76818578b9 chore: remove duplicate v26.0.43 entry with incorrect size 2025-12-30 15:31:51 +01:00
Micha 9070882f38 fix: add XPC service entitlements for Sparkle installer
Add com.apple.security.xpc.aConnectionServices and
com.apple.security.xpc.aStatusServices entitlements to allow sandboxed
app to communicate with Sparkle's Installer and Downloader XPC services.
2025-12-30 15:27:19 +01:00
Micha 541927c30a chore: release 26.0.43 2025-12-30 15:18:43 +01:00
Micha ab3a7ca469 Sparkle test 2025-12-30 15:17:03 +01:00
Micha ee27efc0d4 chore: release 26.0.42 2025-12-30 14:23:40 +01:00
Micha c3f445e3c3 Sparkle test 2025-12-30 14:21:53 +01:00
Micha 215c24d5a2 improvement: enhance Sparkle error logging with error codes
Add error domain and code to abort error messages to help diagnose installation failures.
2025-12-30 13:27:35 +01:00
Micha b96b018f70 chore: release 26.0.41 2025-12-30 13:18:57 +01:00
Micha 65a65939a7 chore: remove local appcast testing scripts
Remove make_local_appcast.sh and serve_local_appcast.sh as they added
complexity without sufficient benefit. Test updates directly with published releases.
2025-12-30 13:15:27 +01:00
Micha 25723b7f07 feat: add in-app Sparkle update logging
- Add published logMessages array to SparkleUpdater to track all update events
- Display logs in the Updates preferences tab with Show/Hide toggle
- Each log entry is timestamped and shows both info and error messages
- Logs persist during session with max 100 entries
- Users can clear logs manually
- Helps diagnose update failures directly in the app UI
2025-12-30 13:12:27 +01:00
Micha 10683ebc73 chore: release 26.0.40 2025-12-30 13:01:20 +01:00
Micha 393bcf27e1 Sparkle test 2025-12-30 12:59:38 +01:00
Micha 839a513fde chore: release 26.0.39 2025-12-30 12:48:55 +01:00
Micha 77e82753ba chore: remove duplicate appcast entry for v26.0.38
Remove the old build 79 entry (from Dec 8) and keep only the new properly-signed build 80.
2025-12-30 12:46:35 +01:00
Micha bbb0b580b0 chore: release 26.0.38 2025-12-08 19:31:41 +01:00
Micha dd225b2b8e Sparkle fixes 2025-12-08 19:30:12 +01:00
Micha 76b01352ac chore: release 26.0.37 2025-12-08 19:07:36 +01:00
Micha fcca8cee38 Sparkle fixes 2025-12-08 19:05:49 +01:00
Micha 94d1b3fec4 chore: release 26.0.36 2025-12-08 18:45:18 +01:00
Micha 4352ae1476 Sparkle fixes 2025-12-08 18:43:48 +01:00
Micha 846e0b149b chore: release 26.0.35 2025-12-08 18:37:49 +01:00
Micha 11ca4dbede Sparkle fixes 2025-12-08 18:36:23 +01:00
Micha 1d8bdfe491 chore: release 26.0.34 2025-12-08 18:27:08 +01:00
Micha 4f5a07822f Sparkle fixes 2025-12-08 18:25:34 +01:00
Micha 67709dfda6 chore: release 26.0.33 2025-12-07 20:22:09 +01:00
Micha 6753226087 Sparkle fixes 2025-12-07 20:20:34 +01:00
Micha a3671acf38 chore: release 26.0.32 2025-12-07 20:18:04 +01:00
Micha 0aa773a0b3 Sparkle fixes 2025-12-07 20:16:37 +01:00
Micha adbc061d0b chore: release 26.0.31 2025-12-07 17:52:45 +01:00
Micha 4deae63d43 Sparkle fixes 2025-12-07 17:50:58 +01:00
Micha b570006074 chore: release 26.0.30 2025-12-07 17:48:38 +01:00
Micha fd0d8d1adb Sparkle fixes 2025-12-07 17:46:59 +01:00
Micha 78d5bd9bd5 chore: release 26.0.29 2025-12-07 17:07:33 +01:00
Micha 091fd4ef38 Sparkle fixes 2025-12-07 17:05:51 +01:00
Micha 656d6403fd chore: release 26.0.28 2025-12-07 17:02:20 +01:00
Micha db4c2aa930 Sparkle fixes 2025-12-07 16:52:13 +01:00
14 changed files with 166 additions and 76 deletions
+19 -1
View File
@@ -1,6 +1,24 @@
# Changelog # Changelog
## Unreleased ## Unreleased
### Fixed
- Fixed Sparkle updater ZIP archive creation: replaced `zip` command with `ditto` to properly preserve app bundle code signatures during extraction, resolving "damaged app" errors on update installation.
- Fixed code signature issues for sandboxed apps by removing entitlements parameter from non-sandboxed builds.
- Fixed Sparkle framework deep code signing to handle complex framework structure.
- Fixed missing XPC service entitlements (`com.apple.security.xpc.aConnectionServices`, `com.apple.security.xpc.aStatusServices`) required for Sparkle installer to communicate with sandboxed app.
### Changed
- Re-enabled app sandbox with minimal entitlements (network.client only) for improved security while maintaining Sparkle update functionality.
- Enhanced Sparkle error logging to include error domain and code information, making update failures easier to diagnose.
- Updated build script to use `ditto -c -k --keepParent` for creating update ZIPs, which properly preserves code signatures that `zip` command breaks.
### Added
- Added in-app Sparkle update logs in Preferences → Updates tab with Show/Hide toggle for real-time debugging of update operations.
- Log entries include timestamps and distinguish between info and error messages.
- Users can clear logs manually and logs persist during the session (max 100 entries).
### Previous Changes
- Flattened the project structure so sources live at the repository root instead of the nested `iKeyMon/` folder and updated the Xcode project accordingly. - Flattened the project structure so sources live at the repository root instead of the nested `iKeyMon/` folder and updated the Xcode project accordingly.
- Fixed build settings (entitlements, preview assets) and placeholder previews to work with the new layout. - Fixed build settings (entitlements, preview assets) and placeholder previews to work with the new layout.
- Migrated the updated API layer and unified `ServerInfo` model from the previous branch. - Migrated the updated API layer and unified `ServerInfo` model from the previous branch.
@@ -10,5 +28,5 @@
- Introduced repository-wide version management via `version.json` + `scripts/sync_version.sh`, ensuring Xcode targets and release artifacts stay aligned. - Introduced repository-wide version management via `version.json` + `scripts/sync_version.sh`, ensuring Xcode targets and release artifacts stay aligned.
- Enhanced `scripts/build_release.sh` to timestamp/harden signatures, notarize DMGs, and optionally publish tagged releases (pre-release by default) with ZIP/DMG assets directly to Gitea when credentials are configured. - Enhanced `scripts/build_release.sh` to timestamp/harden signatures, notarize DMGs, and optionally publish tagged releases (pre-release by default) with ZIP/DMG assets directly to Gitea when credentials are configured.
- Integrated Sparkle (via Swift Package Manager) to handle automatic update checks, downloads, signature verification, and relaunches, replacing the previous custom updater UI. Preferences now simply surface Sparkle's check/download toggles. - Integrated Sparkle (via Swift Package Manager) to handle automatic update checks, downloads, signature verification, and relaunches, replacing the previous custom updater UI. Preferences now simply surface Sparkle's check/download toggles.
- `scripts/build_release.sh` can optionally run Sparkles `generate_appcast` (when signing key and download prefix env vars are set), producing a ready-to-host `appcast.xml` alongside the ZIP/DMG artifacts. - `scripts/build_release.sh` can optionally run Sparkle's `generate_appcast` (when signing key and download prefix env vars are set), producing a ready-to-host `appcast.xml` alongside the ZIP/DMG artifacts.
- Further reduced MainView console noise by removing redundant refresh/onAppear logs. - Further reduced MainView console noise by removing redundant refresh/onAppear logs.
+1 -1
View File
@@ -5,5 +5,5 @@
add a marker for "reboot required" add a marker for "reboot required"
dummy22
111
+1
View File
@@ -60,6 +60,7 @@ GITEA_REPO="iKeyMon"
# optional Sparkle feed helpers: # optional Sparkle feed helpers:
# SPARKLE_EDDSA_KEY_FILE="$HOME/.config/Sparkle/iKeyMon.key" # SPARKLE_EDDSA_KEY_FILE="$HOME/.config/Sparkle/iKeyMon.key"
# SPARKLE_DOWNLOAD_BASE_TEMPLATE="https://git.24unix.net/tracer/iKeyMon/releases/download/v{{VERSION}}" # SPARKLE_DOWNLOAD_BASE_TEMPLATE="https://git.24unix.net/tracer/iKeyMon/releases/download/v{{VERSION}}"
# If you prefer SPARKLE_DOWNLOAD_BASE_URL, it will automatically append `/v<version>` for you.
# SPARKLE_APPCAST_OUTPUT="$ROOT_DIR/Sparkle/appcast.xml" # default # SPARKLE_APPCAST_OUTPUT="$ROOT_DIR/Sparkle/appcast.xml" # default
``` ```
+45 -11
View File
@@ -9,6 +9,7 @@ final class SparkleUpdater: NSObject, ObservableObject {
}() }()
private let logger = Logger(subsystem: "net.24unix.iKeyMon", category: "Sparkle") private let logger = Logger(subsystem: "net.24unix.iKeyMon", category: "Sparkle")
private let verboseLogging: Bool private let verboseLogging: Bool
@Published var logMessages: [String] = []
override init() { override init() {
self.verboseLogging = ProcessInfo.processInfo.environment["SPARKLE_VERBOSE_LOGGING"] == "1" self.verboseLogging = ProcessInfo.processInfo.environment["SPARKLE_VERBOSE_LOGGING"] == "1"
@@ -34,6 +35,7 @@ final class SparkleUpdater: NSObject, ObservableObject {
private func log(_ message: String) { private func log(_ message: String) {
logger.log("\(message, privacy: .public)") logger.log("\(message, privacy: .public)")
addLogMessage("[INFO] \(message)")
if verboseLogging { if verboseLogging {
print("[Sparkle] \(message)") print("[Sparkle] \(message)")
} }
@@ -41,11 +43,21 @@ final class SparkleUpdater: NSObject, ObservableObject {
private func logError(_ message: String) { private func logError(_ message: String) {
logger.error("\(message, privacy: .public)") logger.error("\(message, privacy: .public)")
addLogMessage("[ERROR] \(message)")
if verboseLogging { if verboseLogging {
fputs("[Sparkle][error] \(message)\n", stderr) fputs("[Sparkle][error] \(message)\n", stderr)
} }
} }
private func addLogMessage(_ message: String) {
let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
let timestampedMessage = "[\(timestamp)] \(message)"
logMessages.append(timestampedMessage)
if logMessages.count > 100 {
logMessages.removeFirst()
}
}
private func describe(update item: SUAppcastItem) -> String { private func describe(update item: SUAppcastItem) -> String {
let short = item.displayVersionString let short = item.displayVersionString
let build = item.versionString let build = item.versionString
@@ -53,41 +65,63 @@ final class SparkleUpdater: NSObject, ObservableObject {
} }
} }
@MainActor
extension SparkleUpdater: SPUUpdaterDelegate { extension SparkleUpdater: SPUUpdaterDelegate {
func updater(_ updater: SPUUpdater, didFinishLoading appcast: SUAppcast) { nonisolated func updater(_ updater: SPUUpdater, didFinishLoading appcast: SUAppcast) {
Task { @MainActor in
log("Loaded Sparkle appcast containing \(appcast.items.count) item(s).") log("Loaded Sparkle appcast containing \(appcast.items.count) item(s).")
} }
}
func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) { nonisolated func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) {
Task { @MainActor in
log("Found valid update \(describe(update: item))") log("Found valid update \(describe(update: item))")
} }
}
func updaterDidNotFindUpdate(_ updater: SPUUpdater) { nonisolated func updaterDidNotFindUpdate(_ updater: SPUUpdater) {
Task { @MainActor in
log("No updates available.") log("No updates available.")
} }
}
func updater(_ updater: SPUUpdater, willDownloadUpdate item: SUAppcastItem, with request: NSMutableURLRequest) { nonisolated func updater(_ updater: SPUUpdater, willDownloadUpdate item: SUAppcastItem, with request: NSMutableURLRequest) {
Task { @MainActor in
log("Downloading \(describe(update: item)) from \(request.url?.absoluteString ?? "unknown URL")") log("Downloading \(describe(update: item)) from \(request.url?.absoluteString ?? "unknown URL")")
} }
}
func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) { nonisolated func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) {
Task { @MainActor in
log("Finished downloading \(describe(update: item))") log("Finished downloading \(describe(update: item))")
} }
}
func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) { nonisolated func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) {
Task { @MainActor in
logError("Failed to download \(describe(update: item)): \(error.localizedDescription)") logError("Failed to download \(describe(update: item)): \(error.localizedDescription)")
} }
}
func userDidCancelDownload(_ updater: SPUUpdater) { nonisolated func userDidCancelDownload(_ updater: SPUUpdater) {
Task { @MainActor in
log("User cancelled Sparkle download.") log("User cancelled Sparkle download.")
} }
}
func updater(_ updater: SPUUpdater, willInstallUpdate item: SUAppcastItem) { nonisolated func updater(_ updater: SPUUpdater, willInstallUpdate item: SUAppcastItem) {
Task { @MainActor in
log("Will install update \(describe(update: item))") log("Will install update \(describe(update: item))")
} }
}
func updater(_ updater: SPUUpdater, didAbortWithError error: Error) { nonisolated func updater(_ updater: SPUUpdater, didAbortWithError error: Error) {
logError("Sparkle aborted: \(error.localizedDescription)") Task { @MainActor in
let errorDescription = error as NSError
let details = "Domain: \(errorDescription.domain), Code: \(errorDescription.code), Description: \(error.localizedDescription)"
logError("Sparkle aborted: \(details)")
if let underlying = errorDescription.userInfo[NSUnderlyingErrorKey] as? NSError {
logError("Underlying error: Domain: \(underlying.domain), Code: \(underlying.code), Description: \(underlying.localizedDescription)")
}
}
} }
} }
+2
View File
@@ -26,6 +26,8 @@ struct InfoCell: View {
.font(monospaced ? .system(.body, design: .monospaced) : .body) .font(monospaced ? .system(.body, design: .monospaced) : .body)
} }
} }
// if let subtext { // if let subtext {
// Text(subtext) // Text(subtext)
// .font(.caption) // .font(.caption)
-9
View File
@@ -12,7 +12,6 @@ struct MainView: View {
private static let serverOrderKeyStatic = "serverOrder" private static let serverOrderKeyStatic = "serverOrder"
private static let storedServersKeyStatic = "storedServers" private static let storedServersKeyStatic = "storedServers"
@EnvironmentObject private var sparkleUpdater: SparkleUpdater
@State var showAddServerSheet: Bool = false @State var showAddServerSheet: Bool = false
@State private var serverBeingEdited: Server? @State private var serverBeingEdited: Server?
@State private var serverToDelete: Server? @State private var serverToDelete: Server?
@@ -60,14 +59,6 @@ struct MainView: View {
} }
.help("Add Host") .help("Add Host")
} }
ToolbarItem {
Button {
sparkleUpdater.checkForUpdates()
} label: {
Image(systemName: "square.and.arrow.down")
}
.help("Check for Updates")
}
} }
.navigationTitle("Servers") .navigationTitle("Servers")
.onChange(of: selectedServerID) { .onChange(of: selectedServerID) {
-5
View File
@@ -252,11 +252,6 @@ private struct UpdatesPreferencesView: View {
Label("Check for Updates Now", systemImage: "sparkles") Label("Check for Updates Now", systemImage: "sparkles")
} }
Text("Updates are delivered via Sparkle. Configure your appcast URL and public EdDSA key in Info.plist (keys `SUFeedURL` and `SUPublicEDKey`).")
.font(.caption)
.foregroundColor(.secondary)
.padding(.top, 4)
Spacer() Spacer()
} }
.toggleStyle(.switch) .toggleStyle(.switch)
+15 -15
View File
@@ -3,28 +3,28 @@
<channel> <channel>
<title>iKeyMon</title> <title>iKeyMon</title>
<item> <item>
<title>26.0.27</title> <title>26.0.68</title>
<pubDate>Sun, 07 Dec 2025 16:47:33 +0100</pubDate> <pubDate>Sat, 03 Jan 2026 13:52:33 +0100</pubDate>
<sparkle:version>57</sparkle:version> <sparkle:version>148</sparkle:version>
<sparkle:shortVersionString>26.0.27</sparkle:shortVersionString> <sparkle:shortVersionString>26.0.68</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.2</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>15.2</sparkle:minimumSystemVersion>
<enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/iKeyMon-26.0.27.zip" length="4811492" type="application/octet-stream" sparkle:edSignature="6aEv0ii20pAkIl8kYWNkHM7+8APyDQtsus0SkF3C7/7q2X73HAsrsskNXjiiq0YF6bPVNAEs5y8G8GpwmerrCw=="/> <enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/v26.0.68/iKeyMon-26.0.68.zip" length="2993469" type="application/octet-stream" sparkle:edSignature="M5WBkO4BN8RwMJ0ZU3Ku4CyQllnbEzz9X6MYR4IVX5prO9oyMBGoceHA3C97wZA6+++9u7RnRsKrFvei2CsWBQ=="/>
</item> </item>
<item> <item>
<title>26.0.21</title> <title>26.0.67</title>
<pubDate>Wed, 26 Nov 2025 18:44:41 +0100</pubDate> <pubDate>Sat, 03 Jan 2026 13:36:29 +0100</pubDate>
<sparkle:version>49</sparkle:version> <sparkle:version>146</sparkle:version>
<sparkle:shortVersionString>26.0.21</sparkle:shortVersionString> <sparkle:shortVersionString>26.0.67</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.2</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>15.2</sparkle:minimumSystemVersion>
<enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/v26.0.21/iKeyMon-26.0.21.zip" length="4802995" type="application/octet-stream" sparkle:edSignature="bYXN15YyKlSmHKNXPizEW2WrVXQSgD5XOgbtzOYNL+maG8DB/jZ08A+cYtGgqUeSRd+X6Z5Ue+Tpdn4/ewsFBw=="/> <enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/v26.0.67/iKeyMon-26.0.67.zip" length="2993400" type="application/octet-stream" sparkle:edSignature="kr7vKMSM0002I/Fx2KVFGqNA6uMHb5Ll6Cr8NSG+8/Ct6KkC9dAwcd50xeUVPVJ7UT8lNBVPoBjZoFssgIEPAw=="/>
</item> </item>
<item> <item>
<title>26.0.20</title> <title>26.0.66</title>
<pubDate>Wed, 26 Nov 2025 18:36:41 +0100</pubDate> <pubDate>Sat, 03 Jan 2026 13:28:44 +0100</pubDate>
<sparkle:version>47</sparkle:version> <sparkle:version>144</sparkle:version>
<sparkle:shortVersionString>26.0.20</sparkle:shortVersionString> <sparkle:shortVersionString>26.0.66</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.2</sparkle:minimumSystemVersion> <sparkle:minimumSystemVersion>15.2</sparkle:minimumSystemVersion>
<enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/v26.0.20/iKeyMon-26.0.20.zip" length="4802865" type="application/octet-stream" sparkle:edSignature="hCJu2I1Db/TaU6pCs1gZi9EO5igr49Fjt/VNnyD8+jm45WINuhzGc4lShcLPxUQTy4iNHnVhmOPYwlthVMXPAg=="/> <enclosure url="https://git.24unix.net/tracer/iKeyMon/releases/download/v26.0.66/iKeyMon-26.0.66.zip" length="2993408" type="application/octet-stream" sparkle:edSignature="qEMNzN6b4me5I9hfDMYtQiR++6GUO7fP1WW1nyj8ePCLzbHbWy2ON8TSJBVoVO7Eg3OXDeKLI34D/sUNOdkqBQ=="/>
</item> </item>
</channel> </channel>
</rss> </rss>
+34 -12
View File
@@ -3,9 +3,11 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REMOTE_NAME="${1:-origin}" REMOTE_NAME="${1:-origin}"
QUIET_RELEASE="${QUIET_RELEASE:-1}"
RELEASE_LOG="${RELEASE_LOG:-$ROOT_DIR/build/release.log}"
if [[ -n "${SKIP_RELEASE:-}" ]]; then if [[ -n "${SKIP_RELEASE:-}" ]]; then
echo "⚙️ SKIP_RELEASE set — skipping automated release build." echo "release: skipped (SKIP_RELEASE=1)"
exit 0 exit 0
fi fi
@@ -34,29 +36,49 @@ if [[ "$should_release" != true ]]; then
exit 0 exit 0
fi fi
echo "🚀 Detected push to master — bumping version and building release..." if [[ "$QUIET_RELEASE" == "1" ]]; then
mkdir -p "$(dirname "$RELEASE_LOG")"
: >"$RELEASE_LOG"
fi
run_logged() {
if [[ "$QUIET_RELEASE" == "1" ]]; then
"$@" >>"$RELEASE_LOG" 2>&1
else
"$@"
fi
}
if [[ "$QUIET_RELEASE" == "1" ]]; then
NEW_VERSION="$("$ROOT_DIR/scripts/bump_version.sh" 2>>"$RELEASE_LOG" | tee -a "$RELEASE_LOG")"
else
NEW_VERSION="$("$ROOT_DIR/scripts/bump_version.sh")" NEW_VERSION="$("$ROOT_DIR/scripts/bump_version.sh")"
echo "🔢 marketing_version -> ${NEW_VERSION}" fi
"$ROOT_DIR/scripts/sync_version.sh" run_logged "$ROOT_DIR/scripts/sync_version.sh"
git -C "$ROOT_DIR" add "$ROOT_DIR/version.json" "$ROOT_DIR/iKeyMon.xcodeproj/project.pbxproj" git -C "$ROOT_DIR" add "$ROOT_DIR/version.json" "$ROOT_DIR/iKeyMon.xcodeproj/project.pbxproj"
"$ROOT_DIR/scripts/build_release.sh" echo "release: building v${NEW_VERSION}..."
if ! run_logged "$ROOT_DIR/scripts/build_release.sh"; then
echo "release: failed (log: $RELEASE_LOG)"
exit 1
fi
git -C "$ROOT_DIR" add "$ROOT_DIR/version.json" "$ROOT_DIR/iKeyMon.xcodeproj/project.pbxproj" "$ROOT_DIR/Sparkle/appcast.xml" git -C "$ROOT_DIR" add "$ROOT_DIR/version.json" "$ROOT_DIR/iKeyMon.xcodeproj/project.pbxproj" "$ROOT_DIR/Sparkle/appcast.xml"
if git -C "$ROOT_DIR" diff --cached --quiet; then if git -C "$ROOT_DIR" diff --cached --quiet; then
echo "⚠️ No release changes detected; skipping release commit." echo "release: no changes detected; skipping commit"
else else
git -C "$ROOT_DIR" commit -m "chore: release ${NEW_VERSION}" run_logged git -C "$ROOT_DIR" commit -m "chore: release ${NEW_VERSION}" || {
echo "📝 Committed release ${NEW_VERSION}." echo "release: commit failed (log: $RELEASE_LOG)"
exit 1
}
fi fi
echo "📤 Pushing release commit..." if SKIP_RELEASE=1 git -C "$ROOT_DIR" push --quiet "$REMOTE_NAME" "${release_local_ref:-refs/heads/master}:${release_remote_ref:-refs/heads/master}"; then
if SKIP_RELEASE=1 git -C "$ROOT_DIR" push "$REMOTE_NAME" "${release_local_ref:-refs/heads/master}:${release_remote_ref:-refs/heads/master}"; then echo "release: success v${NEW_VERSION}"
echo "✅ Release ${NEW_VERSION} pushed. Original push cancelled (already done)."
exit 1 exit 1
else else
echo "❌ Failed to push release ${NEW_VERSION}. Please resolve manually." echo "release: push failed (log: $RELEASE_LOG)"
exit 1 exit 1
fi fi
+2
View File
@@ -6,5 +6,7 @@
<string>https://git.24unix.net/tracer/iKeyMon/raw/branch/master/Sparkle/appcast.xml</string> <string>https://git.24unix.net/tracer/iKeyMon/raw/branch/master/Sparkle/appcast.xml</string>
<key>SUPublicEDKey</key> <key>SUPublicEDKey</key>
<string>EgJgrOGQ79L5me616jA7kDCEOgx+Rg11uYLYLLIyzTI=</string> <string>EgJgrOGQ79L5me616jA7kDCEOgx+Rg11uYLYLLIyzTI=</string>
<key>SUEnableInstallerLauncherService</key>
<false/>
</dict> </dict>
</plist> </plist>
+5 -3
View File
@@ -3,10 +3,12 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <false/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict> </dict>
</plist> </plist>
+16 -4
View File
@@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
5221016D2EE5E82700D04952 /* appcast.xml in Resources */ = {isa = PBXBuildFile; fileRef = 5221016B2EE5E82700D04952 /* appcast.xml */; };
52A9B79F2EC8E7EE004DD4A2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */; }; 52A9B79F2EC8E7EE004DD4A2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */; };
52A9B8222EC8FA8A004DD4A2 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */; }; 52A9B8222EC8FA8A004DD4A2 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */; };
52A9B9722ECF751C004DD4A2 /* signing.env.example in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B9712ECF751C004DD4A2 /* signing.env.example */; }; 52A9B9722ECF751C004DD4A2 /* signing.env.example in Resources */ = {isa = PBXBuildFile; fileRef = 52A9B9712ECF751C004DD4A2 /* signing.env.example */; };
@@ -28,6 +29,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
5203C24D2D997D2800576D4A /* iKeyMon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iKeyMon.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5203C24D2D997D2800576D4A /* iKeyMon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iKeyMon.app; sourceTree = BUILT_PRODUCTS_DIR; };
5221016B2EE5E82700D04952 /* appcast.xml */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = appcast.xml; sourceTree = "<group>"; };
52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 52A9B7872EC8E7EE004DD4A2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
52A9B7882EC8E7EE004DD4A2 /* iKeyMon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iKeyMon.entitlements; sourceTree = "<group>"; }; 52A9B7882EC8E7EE004DD4A2 /* iKeyMon.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iKeyMon.entitlements; sourceTree = "<group>"; };
52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; }; 52A9B8212EC8FA8A004DD4A2 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
@@ -76,6 +78,7 @@
52A9B9712ECF751C004DD4A2 /* signing.env.example */, 52A9B9712ECF751C004DD4A2 /* signing.env.example */,
52A9BEC92ED3874F004DD4A2 /* README.md */, 52A9BEC92ED3874F004DD4A2 /* README.md */,
52A9BD122ED37E08004DD4A2 /* Frameworks */, 52A9BD122ED37E08004DD4A2 /* Frameworks */,
5221016C2EE5E82700D04952 /* Sparkle */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -87,6 +90,14 @@
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
5221016C2EE5E82700D04952 /* Sparkle */ = {
isa = PBXGroup;
children = (
5221016B2EE5E82700D04952 /* appcast.xml */,
);
path = Sparkle;
sourceTree = "<group>";
};
52A9BD122ED37E08004DD4A2 /* Frameworks */ = { 52A9BD122ED37E08004DD4A2 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -166,6 +177,7 @@
files = ( files = (
52A9B8222EC8FA8A004DD4A2 /* CHANGELOG.md in Resources */, 52A9B8222EC8FA8A004DD4A2 /* CHANGELOG.md in Resources */,
52A9BECA2ED3874F004DD4A2 /* README.md in Resources */, 52A9BECA2ED3874F004DD4A2 /* README.md in Resources */,
5221016D2EE5E82700D04952 /* appcast.xml in Resources */,
52A9B79F2EC8E7EE004DD4A2 /* Assets.xcassets in Resources */, 52A9B79F2EC8E7EE004DD4A2 /* Assets.xcassets in Resources */,
52A9B9722ECF751C004DD4A2 /* signing.env.example in Resources */, 52A9B9722ECF751C004DD4A2 /* signing.env.example in Resources */,
); );
@@ -310,7 +322,7 @@
CODE_SIGN_ENTITLEMENTS = iKeyMon.entitlements; CODE_SIGN_ENTITLEMENTS = iKeyMon.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 57; CURRENT_PROJECT_VERSION = 148;
DEVELOPMENT_ASSET_PATHS = "\"Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Preview Content\"";
DEVELOPMENT_TEAM = Q5486ZVAFT; DEVELOPMENT_TEAM = Q5486ZVAFT;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -325,7 +337,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 26.0.27; MARKETING_VERSION = 26.0.68;
PRODUCT_BUNDLE_IDENTIFIER = net.24unix.iKeyMon; PRODUCT_BUNDLE_IDENTIFIER = net.24unix.iKeyMon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
@@ -341,7 +353,7 @@
CODE_SIGN_ENTITLEMENTS = iKeyMon.entitlements; CODE_SIGN_ENTITLEMENTS = iKeyMon.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 57; CURRENT_PROJECT_VERSION = 148;
DEVELOPMENT_ASSET_PATHS = "\"Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Preview Content\"";
DEVELOPMENT_TEAM = Q5486ZVAFT; DEVELOPMENT_TEAM = Q5486ZVAFT;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -356,7 +368,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 26.0.27; MARKETING_VERSION = 26.0.68;
PRODUCT_BUNDLE_IDENTIFIER = net.24unix.iKeyMon; PRODUCT_BUNDLE_IDENTIFIER = net.24unix.iKeyMon;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
+16 -5
View File
@@ -32,8 +32,13 @@ generate_appcast() {
local download_prefix="" local download_prefix=""
if [[ -n "${SPARKLE_DOWNLOAD_BASE_TEMPLATE:-}" ]]; then if [[ -n "${SPARKLE_DOWNLOAD_BASE_TEMPLATE:-}" ]]; then
download_prefix="${SPARKLE_DOWNLOAD_BASE_TEMPLATE//\{\{VERSION\}\}/$VERSION}" download_prefix="${SPARKLE_DOWNLOAD_BASE_TEMPLATE//\{\{VERSION\}\}/$VERSION}"
else elif [[ -n "${SPARKLE_DOWNLOAD_BASE_URL:-}" ]]; then
download_prefix="${SPARKLE_DOWNLOAD_BASE_URL:-}" download_prefix="${SPARKLE_DOWNLOAD_BASE_URL%/}/v${VERSION}"
fi
# Ensure the version segment is present to match Gitea's /download/vX.Y.Z/ layout.
if [[ -n "$download_prefix" ]] && [[ "$download_prefix" != *"/$VERSION"* ]]; then
download_prefix="${download_prefix%/}/v${VERSION}"
fi fi
if [[ -z "$generator" || -z "${SPARKLE_EDDSA_KEY_FILE:-}" || -z "$download_prefix" ]]; then if [[ -z "$generator" || -z "${SPARKLE_EDDSA_KEY_FILE:-}" || -z "$download_prefix" ]]; then
@@ -154,6 +159,14 @@ if [[ ! -d "$APP_PATH" ]]; then
fi fi
if [[ -n "${CODESIGN_IDENTITY:-}" ]]; then if [[ -n "${CODESIGN_IDENTITY:-}" ]]; then
echo "🔏 Codesigning Sparkle framework..."
codesign \
--force \
--options runtime \
--timestamp \
--sign "$CODESIGN_IDENTITY" \
"$APP_PATH/Contents/Frameworks/Sparkle.framework"
echo "🔏 Codesigning app with identity: $CODESIGN_IDENTITY" echo "🔏 Codesigning app with identity: $CODESIGN_IDENTITY"
codesign \ codesign \
--deep \ --deep \
@@ -184,9 +197,7 @@ print(data.get("marketing_version", "dev"))
PY PY
)" )"
ZIP_NAME="iKeyMon-${VERSION}.zip" ZIP_NAME="iKeyMon-${VERSION}.zip"
pushd "$(dirname "$APP_PATH")" >/dev/null ditto -c -k --keepParent "$APP_PATH" "$ARTIFACTS_DIR/$ZIP_NAME"
zip -r "$ARTIFACTS_DIR/$ZIP_NAME" "$(basename "$APP_PATH")"
popd >/dev/null
DMG_NAME="iKeyMon-${VERSION}.dmg" DMG_NAME="iKeyMon-${VERSION}.dmg"
hdiutil create -volname "iKeyMon" -srcfolder "$STAGING_DIR" -ov -format UDZO "$ARTIFACTS_DIR/$DMG_NAME" hdiutil create -volname "iKeyMon" -srcfolder "$STAGING_DIR" -ov -format UDZO "$ARTIFACTS_DIR/$DMG_NAME"
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"marketing_version": "26.0.27" "marketing_version": "26.0.68"
} }