Skip to main content

Starsky Desktop — macOS

Native macOS desktop app wrapping the Starsky photo-management backend inside a WKWebView.
See SPEC.md for the full architecture specification.


Getting Started (local development)

Prerequisites

  • macOS 13 or later
  • Xcode 15 or later (xcode-select -p should point to /Applications/Xcode.app/…)
  • xcodegen — generates the Xcode project from project.yml
brew install xcodegen

First-time setup

# From the repo root
cd mac
xcodegen generate # creates starsky.xcodeproj
open starsky.xcodeproj # or double-click in Finder

Run xcodegen generate again any time you add or remove Swift source files, or after pulling changes that modify project.yml.

Build & run from the command line

cd mac
xcodebuild build \
-project starsky.xcodeproj \
-scheme starsky \
-configuration Debug \
-destination 'platform=macOS' \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

Run the tests

cd mac
xcodebuild test \
-project starsky.xcodeproj \
-scheme starskyTests \
-destination 'platform=macOS' \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

Expected output: ** TEST SUCCEEDED ** (57 tests, 0 failures).

Bundled backend

The app looks for the Starsky ASP.NET Core binary at:

starsky.app/Contents/MacOS/runtime-starsky-osx-arm64/starsky # Apple Silicon
starsky.app/Contents/MacOS/runtime-starsky-osx-x64/starsky # Intel

These are copied at build time from starskydesktop/runtime-starsky-mac-arm64/ and starskydesktop/runtime-starsky-mac-x64/ (if present). A build warning is emitted when they are missing; Local mode will not work without them.


Before Releasing

1. Generate a Sparkle EdDSA keypair

Sparkle 2 requires an EdDSA key to sign update packages. Do this once and store the private key securely (it never goes in the repo).

# Download the Sparkle release and extract generate_keys
curl -L https://github.com/sparkle-project/Sparkle/releases/latest/download/Sparkle-2.x.x.tar.xz | tar -xJ
./bin/generate_keys

The tool prints:

Private key saved to ~/Library/Preferences/Sparkle/Sparkle_private_key
Public key (add to Info.plist): <base64-string>

Open starsky/Info.plist and set:

<key>SUPublicEDKey</key>
<string>PASTE_PUBLIC_KEY_HERE</string>

2. Set your Apple Developer Team ID

In project.yml, replace the empty DEVELOPMENT_TEAM value:

DEVELOPMENT_TEAM: "XXXXXXXXXX" # your 10-character Team ID

Also fill in the teamID field in ExportOptions.plist:

<key>teamID</key>
<string>XXXXXXXXXX</string>

3. Install the Developer ID certificate

Ensure "Developer ID Application: <your name> (<team-id>)" is installed in Keychain Access.
Download it from developer.apple.com/account → Certificates if needed.

4. Set up GitHub secrets for CI

Go to Settings → Secrets and variables → Actions in your GitHub repo and add:

Secret nameValue
STARSKY_APPLE_IDYour Apple ID email (e.g. you@example.com)
STARSKY_APPLE_TEAM_IDYour 10-character Team ID
STARSKY_NOTARYTOOL_APP_PASSWORDAn app-specific password from appleid.apple.com → App-Specific Passwords
STARSKY_MACOS_CERTIFICATEBase64-encoded Developer ID Application .p12: base64 -i cert.p12 | pbcopy
STARSKY_MACOS_CERTIFICATE_PWDPassword that protects the .p12 file
STARSKY_MACOS_KEYCHAIN_PASSWORDAny strong random string — used only for the throwaway CI keychain

Verifying CI signing

Watch the build_mac_native, build_mac_arm64, and build_mac_x64 jobs after pushing a tag.

  • Archive step — if the keychain import worked, xcodebuild archive completes without "No signing certificate found." A failure here means STARSKY_MACOS_CERTIFICATE or STARSKY_MACOS_CERTIFICATE_PWD is wrong.
  • Notarize step — signing and notarizing are independent; a passing archive does not guarantee notarization succeeds. If notarization fails, check STARSKY_APPLE_ID, STARSKY_APPLE_TEAM_ID, and STARSKY_NOTARYTOOL_APP_PASSWORD.

5. Create the Sparkle appcast

After your first signed build, generate an appcast XML file and host it at the SUFeedURL configured in starsky/Info.plist:

https://qdraw.nl/special/starsky/appcast-macos.xml

Use sparkle-generate-appcast (included in the Sparkle distribution):

./bin/generate_appcast /path/to/release/folder/

Upload the resulting appcast.xml to your web server at the URL above.

6. Release build (manual)

cd mac
xcodegen generate

# Archive
xcodebuild archive \
-project starsky.xcodeproj \
-scheme starsky \
-configuration Release \
-archivePath ../build/starsky.xcarchive \
ARCHS="arm64 x86_64"

# Export signed app
xcodebuild -exportArchive \
-archivePath ../build/starsky.xcarchive \
-exportPath ../build/ \
-exportOptionsPlist ExportOptions.plist

# Create DMG
brew install create-dmg
create-dmg \
--volname "Starsky" \
--window-size 600 400 \
--icon-size 100 \
--icon "starsky.app" 175 190 \
--app-drop-link 425 190 \
../build/starsky.dmg \
"../build/starsky.app"

# Notarize
xcrun notarytool submit ../build/starsky.dmg \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$NOTARYTOOL_APP_PASSWORD" \
--wait

# Staple
xcrun stapler staple ../build/starsky.dmg

# Verify
spctl -a -vvv ../build/starsky.app

Release builds on tagged commits are automated via .github/workflows/desktop-macos-pr-build.yml.


Project structure

mac/
├── SPEC.md full architecture specification
├── README.md this file
├── project.yml xcodegen spec (source of truth for the Xcode project)
├── ExportOptions.plist Developer ID export settings for notarization
├── starsky/
│ ├── App/
│ │ ├── AppDelegate.swift startup / shutdown / menu bar
│ │ └── ApplicationInfo.swift version string from bundle
│ ├── Models/ Codable data types
│ ├── Services/ all business logic (no UI)
│ ├── Windows/ NSWindowController subclasses + WKWebView
│ ├── WindowManager.swift manages open MainWindowController instances
│ └── Resources/Assets.xcassets AppIcon (populate before release)
└── starskyTests/
├── Helpers/FakeURLProtocol.swift offline HTTP testing
├── FakeCreateAn/ fake backend binary helper
├── Models/ model tests
└── Services/ service tests (57 tests total)