Shipunk

Sparkle Integration

Configure Sparkle to use Shipunk for updates

Sparkle Integration

This guide covers how to integrate Sparkle with Shipunk in your Mac app.

Prerequisites

  • Xcode project with Sparkle framework installed
  • A Shipunk account with an app created
  • Your app's EdDSA public key (from Shipunk dashboard)

Adding Sparkle to your app

If you haven't already, add Sparkle using Swift Package Manager:

  1. In Xcode, go to File → Add Package Dependencies
  2. Enter: https://github.com/sparkle-project/Sparkle
  3. Select version 2.x

Configuring Info.plist

Add these keys to your Info.plist:

<!-- Appcast URL from Shipunk -->
<key>SUFeedURL</key>
<string>https://shipunk.dev/appcast/YOUR_APP_SLUG.xml</string>

<!-- EdDSA public key from Shipunk dashboard -->
<key>SUPublicEDKey</key>
<string>YOUR_EDDSA_PUBLIC_KEY</string>

<!-- Optional: Allow automatic updates -->
<key>SUAutomaticallyUpdate</key>
<true/>

<!-- Optional: Check for updates on launch -->
<key>SUEnableAutomaticChecks</key>
<true/>

Finding your public key

  1. Go to shipunk.dev/dashboard
  2. Click on your app
  3. Copy the EdDSA Public Key from the settings

Code setup

SwiftUI

import SwiftUI
import Sparkle

@main
struct MyApp: App {
    private let updaterController: SPUStandardUpdaterController
    
    init() {
        updaterController = SPUStandardUpdaterController(
            startingUpdater: true,
            updaterDelegate: nil,
            userDriverDelegate: nil
        )
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandGroup(after: .appInfo) {
                CheckForUpdatesView(updater: updaterController.updater)
            }
        }
    }
}

struct CheckForUpdatesView: View {
    @ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
    private let updater: SPUUpdater
    
    init(updater: SPUUpdater) {
        self.updater = updater
        self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
    }
    
    var body: some View {
        Button("Check for Updates…", action: updater.checkForUpdates)
            .disabled(!checkForUpdatesViewModel.canCheckForUpdates)
    }
}

final class CheckForUpdatesViewModel: ObservableObject {
    @Published var canCheckForUpdates = false
    
    init(updater: SPUUpdater) {
        updater.publisher(for: \.canCheckForUpdates)
            .assign(to: &$canCheckForUpdates)
    }
}

AppKit

import Cocoa
import Sparkle

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    let updaterController = SPUStandardUpdaterController(
        startingUpdater: true,
        updaterDelegate: nil,
        userDriverDelegate: nil
    )
    
    @IBAction func checkForUpdates(_ sender: Any) {
        updaterController.checkForUpdates(sender)
    }
}

Testing updates

  1. Build your app with version 1.0.0
  2. Upload version 1.1.0 to Shipunk
  3. Run the 1.0.0 build and check for updates

The update dialog should appear with download options.

Troubleshooting

"No updates available" even though there is one

  • Check that the version in your Info.plist (CFBundleShortVersionString) is lower than the uploaded version
  • Verify your appcast URL is correct
  • Check the Sparkle console logs

Signature verification failed

  • Make sure the SUPublicEDKey in your app matches the key in Shipunk
  • Re-download your public key from the dashboard

Update downloads but won't install

  • Ensure your app is code-signed
  • Check that the downloaded file isn't corrupted

Release notes

Shipunk supports HTML release notes. When uploading, you can include:

shipunk release upload MyApp.dmg \
  -a myapp \
  -v 1.1.0 \
  -n "<h2>What's New</h2><ul><li>New feature</li><li>Bug fix</li></ul>"

Or use a markdown file (converted to HTML):

shipunk release upload MyApp.dmg \
  -a myapp \
  -v 1.1.0 \
  --notes-file CHANGELOG.md