Skip to content

maxhewett/ShipHook

Repository files navigation

ShipHook

ShipHook

ShipHook is a macOS app used to automate signing & deployment of other macOS apps. ShipHook monitors GitHub repositories and then builds, signs & notarises apps. Once notarised, ShipHook utilises the Sparkle framework to push app updates via appcast.xml hosted on GitHub pages. The main use case for ShipHook is to replace the manual step of signing & building apps when multiple collaborators are working on a macOS app. This allows everyone to test, build, and collaborate via git, but only automate the last step when a build is ready for deployment.

shpreview

Project Layout

What ShipHook Automates

  1. Poll GitHub for new commits on configured branches.
  2. Ignore ShipHook-managed appcast commits marked with [shiphook skip].
  3. Queue builds so only one repository pipeline runs at a time.
  4. Sync the local checkout to the latest GitHub branch state without detaching HEAD.
  5. Detect beta releases from commit markers and route them to a separate beta feed.
  6. Inspect the target project and plan the next Sparkle-safe build version.
  7. Build using xcodebuild archive or a custom shell command.
  8. Publish the release artifact, appcast, and optional appcast commit push.
  9. Surface live pipeline phase, status, and log output in the app.

Dashboard Features

  • Multiple repositories in a single config
  • Guided add-repository wizard
  • Default appcast URL inference using https://<owner>.github.io/<repo>/appcast.xml
  • Release build planning against the latest appcast item
  • Local signing identity detection
  • Per-repo signing overrides
  • Live log tailing from .shiphook/logs/<repo-id>-latest.log
  • Reset for stale in-progress build state
  • Organizer-visible Xcode archive output
  • Automatic Sparkle release notes generated from the triggering commit message
  • Beta channel support triggered by commit markers

Configuration

On first launch, ShipHook writes config to:

~/Library/Application Support/ShipHook/config.json

You can manage that config from the ShipHook dashboard instead of editing JSON directly.

Each repository supports:

  • GitHub owner, repo, branch
  • local checkout path
  • optional working directory and release notes path override
  • xcodeArchive build mode
  • shell build mode
  • per-repo environment values
  • Sparkle appcast URL and auto-increment build behavior
  • signing overrides for team, identity, and sign style
  • a publish command fed by ShipHook environment variables

Publish Environment

ShipHook injects these environment variables into publish commands:

SHIPHOOK_WORKSPACE_ROOT
SHIPHOOK_REPO_ID
SHIPHOOK_REPO_NAME
SHIPHOOK_GITHUB_OWNER
SHIPHOOK_GITHUB_REPO
SHIPHOOK_BRANCH
SHIPHOOK_SHA
SHIPHOOK_SHORT_SHA
SHIPHOOK_VERSION
SHIPHOOK_RELEASE_CHANNEL
SHIPHOOK_LOCAL_CHECKOUT_PATH
SHIPHOOK_RELEASE_NOTES_PATH
SHIPHOOK_ARTIFACT_PATH
SHIPHOOK_APPCAST_URL
SHIPHOOK_BUNDLED_PUBLISH_SCRIPT

If Release Notes Path Override is empty, ShipHook generates an HTML release-notes page from the commit title and body for the SHA being published, then sets SHIPHOOK_RELEASE_NOTES_PATH to that generated file automatically.

If the commit message contains [beta], [shiphook beta], [pre-release], or [prerelease], ShipHook sets SHIPHOOK_RELEASE_CHANNEL=beta and publishes to the beta channel instead of stable.

Example Publish Command

bash "$SHIPHOOK_BUNDLED_PUBLISH_SCRIPT" \
  --version "$SHIPHOOK_VERSION" \
  --artifact "$SHIPHOOK_ARTIFACT_PATH" \
  --app-name "ExampleApp" \
  --repo-owner "$SHIPHOOK_GITHUB_OWNER" \
  --repo-name "$SHIPHOOK_GITHUB_REPO" \
  --channel "$SHIPHOOK_RELEASE_CHANNEL" \
  --release-notes "$SHIPHOOK_RELEASE_NOTES_PATH" \
  --docs-dir "$SHIPHOOK_LOCAL_CHECKOUT_PATH/docs" \
  --releases-dir "$SHIPHOOK_LOCAL_CHECKOUT_PATH/release-artifacts" \
  --working-dir "$SHIPHOOK_LOCAL_CHECKOUT_PATH"

Beta builds publish to:

  • docs/beta/appcast.xml
  • docs/beta/release-notes/...
  • GitHub prereleases tagged like v1.5.1-beta

Appcast Commit Loop Prevention

publish_sparkle_release.sh can commit and push appcast.xml updates back to the repo. Those commits are tagged like:

chore(shiphook): update appcast for AppName 1.2.3 [shiphook skip]

ShipHook ignores commits containing [shiphook skip] or [skip shiphook], which prevents infinite rebuild loops.

Signing

For Sparkle-distributed macOS apps, ShipHook expects Developer ID Application signing for release archives.

Use:

  • DEVELOPER_ID_SETUP.md to create and install the certificate
  • the signing section in the dashboard to pick the local identity
  • manual signing overrides when the target project does not already archive correctly on its own

ShipHook Self-Updates

ShipHook itself now includes a Sparkle updater entry point using SPUStandardUpdaterController.

You can trigger it from:

  • the app menu: Check for Updates...
  • the menu bar extra: Check for Updates...

ShipHook currently ships with these Sparkle Info.plist-style build settings:

Important:

  • ShipHook's own release feed must exist and be signed correctly for Sparkle to work

The updater implementation lives in:

Build

xcodebuild -project /Users/max/Developer/ShipHook/ShipHook.xcodeproj \
  -scheme ShipHook \
  -configuration Debug \
  -derivedDataPath /Users/max/Developer/ShipHook/.build-xcode \
  build

Sparkle Integration Snippet

ShipHook uses the same Sparkle pattern you would use in a normal SwiftUI app:

import Sparkle

@MainActor
final class AppUpdater: ObservableObject {
    private let updaterController: SPUStandardUpdaterController?

    init() {
        updaterController = SPUStandardUpdaterController(
            startingUpdater: true,
            updaterDelegate: nil,
            userDriverDelegate: nil
        )
    }

    func checkForUpdates() {
        updaterController?.checkForUpdates(nil)
    }
}

About

ShipHook is a native macOS app and menu bar companion for monitoring GitHub repositories, detecting new pushes, building release archives, and publishing Sparkle appcasts and release artifacts.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors