macOS-Apps verteilen
Flutter-macOS-Apps über den Mac App Store oder direkt verteilen: Developer ID, Notarisierung, DMG per GitHub-Release oder Webhook, Sparkle-Updates.
Die Build Station bringt eine gebaute macOS-App (flutter build macos, Output-Typ macOS (.app)) auf einem von drei Wegen zu deinen Nutzern. Den Weg wählst du pro Projekt in Projekt bearbeiten › Upload › Store-Upload einrichten im Tab macOS unter Verteilung. Im Projekt-Editor führt auch macOS-Verteilung einrichten … dorthin.
| Weg | Was passiert | Braucht zusätzlich |
|---|---|---|
| Mac App Store | archivieren, für App Store Connect exportieren, mit xcrun altool hochladen |
Team und automatische Signierung im Xcode-Projekt, App in der Sandbox |
| GitHub-Release (DMG) | mit Developer ID signieren, DMG bauen, notarisieren, als Release v<Version> per gh veröffentlichen |
Zertifikat „Developer ID Application”, gh auth login |
| HTTPS-Webhook (DMG) | wie GitHub, aber ein signierter POST an deinen eigenen Server | Zertifikat, Webhook-Adresse und Geheimnis |
Alle drei Wege nutzen den App-Store-Connect-Schlüssel aus dem Tab „App Store”, also dieselben Zugangsdaten wie der iOS-Upload. Beim Direktvertrieb dient er der Notarisierung. Fehlt er, weist der Tab darauf hin. Wie du den Schlüssel anlegst, steht unter App Store und TestFlight.
Der automatische Upload nach einem erfolgreichen Build funktioniert wie bei den anderen Stores, siehe Artefakte und Upload.
Mac App Store
- In App Store Connect bei der App die Plattform macOS hinzufügen oder eine eigene macOS-App mit derselben Bundle-ID anlegen.
- Im Xcode-Projekt (
macos/Runner.xcworkspace) ein Team setzen und „Automatically manage signing” einschalten. Die App muss in der Sandbox laufen. - Zertifikat und Provisioning-Profil holt Xcode beim Export mit dem API-Schlüssel selbst. Soll Xcode dabei auch Zertifikate anlegen, braucht der Schlüssel die Rolle Admin. Sonst legst du die Zertifikate einmal in Xcode unter Settings › Accounts an.
Der Ablauf ist xcodebuild archive, dann -exportArchive für App Store Connect, dann xcrun altool --upload-app -t macos. Validiert wird vorab wie bei iOS. Build-Nummern zählen für macOS getrennt von iOS.
Direktvertrieb mit Developer ID
Außerhalb des Stores muss eine Mac-App mit einem Zertifikat Developer ID Application signiert und von Apple notarisiert sein, sonst blockiert macOS sie.
Einrichten
- Im Apple Developer Portal ein Zertifikat „Developer ID Application” anlegen. Das darf nur der Account Holder.
- Das Zertifikat in den Schlüsselbund des Macs legen, auf dem die Station läuft.
- Im Tab macOS die Verteilung wählen. Signier-Identität (optional) bleibt leer, wenn genau eine „Developer ID Application” im Schlüsselbund liegt.
Für GitHub-Release (DMG):
- Unter Repository (owner/repo) das Ziel eintragen.
- Hochgeladen wird mit der GitHub-CLI. Einmal
gh auth loginauf diesem Mac ausführen.
Für HTTPS-Webhook (DMG):
- Webhook-Adresse (https://…) deines Servers eintragen.
- Geheimnis für die Signatur eintragen oder mit Erzeugen und kopieren erzeugen und auf dem Server hinterlegen. Es liegt im Schlüsselbund.
- Für Sparkle die Download-Adresse des Servers angeben, unter der DMG und
appcast.xmldanach liegen.
Was beim Upload passiert
- Signieren: Eine Kopie der App wird von innen nach außen signiert, also erst die Sparkle-Bestandteile, Frameworks und Bibliotheken, dann die App. Mit Hardened Runtime und Zeitstempel. Die Entitlements kommen aus
macos/Runner/Release.entitlementsdes Projekts. - DMG: Ein DMG mit Verknüpfung zu „Programme”, ebenfalls signiert. Es liegt neben dem Artefakt im Sammelordner unter
distribution/. - Notarisieren: mit
xcrun notarytool submit --wait. Entscheidend ist der Status „Accepted”. Lehnt Apple ab, steht Apples Log im Upload-Log. Danach heftetstapler stapledas Ticket an. - Sparkle (optional): EdDSA-Signatur des DMG und eine
appcast.xmldaneben. - Veröffentlichen: als GitHub-Release oder per Webhook. Gibt es auf GitHub schon ein Release derselben Version, werden seine Dateien ersetzt.
Das Developer-ID-Zertifikat steht mit Ablaufdatum in der Übersicht. Läuft es ab, laufen schon notarisierte Versionen weiter, nur neue lassen sich nicht mehr signieren.
Sparkle-Updates
Mit Sparkle findet die installierte App neue Versionen selbst. Dafür braucht deine App das Sparkle-Framework, in Flutter am einfachsten über das Paket auto_updater. Findet die Station Sparkle nicht im Projekt, warnt sie.
So richtest du es ein:
- Im Tab macOS Updates über Sparkle anbieten einschalten.
- Erzeugen legt ein Schlüsselpaar an. Hast du schon einen Schlüssel aus Sparkles
generate_keys -x, übernimmst du ihn mit Importieren …. Der private Schlüssel geht in den Schlüsselbund. - In Info.plist eintragen schreibt den öffentlichen Schlüssel (
SUPublicEDKey), die Feed-Adresse (SUFeedURL) undSUEnableAutomaticChecksinmacos/Runner/Info.plist. - Die Änderung ins Repository committen. Wirksam ist sie ab dem nächsten Build.
Die Feed-Adresse ergibt sich aus dem Ziel:
| Ziel | SUFeedURL |
|---|---|
| GitHub | https://github.com/<owner>/<repo>/releases/latest/download/appcast.xml |
| Webhook | <Download-Adresse>/appcast.xml |
Erzeug den Schlüssel nicht neu, wenn schon Versionen draußen sind. Die kennen nur den alten Schlüssel und nehmen neue Updates nicht an. Sie müssen dann einmal von Hand aktualisiert werden.
Der Appcast enthält nur das jüngste Update. sparkle:version ist die Build-Nummer (CFBundleVersion), danach entscheidet Sparkle, ob etwas neuer ist. Die Release Notes aus der Upload-Rückfrage erscheinen im Update-Fenster.
Das Webhook-Protokoll
Für den eigenen Server: Die Station schickt einen POST mit multipart/form-data.
| Feld | Inhalt |
|---|---|
metadata |
JSON mit event (macos.release), app, bundleId, version, build, releaseNotes, downloadUrl und files[] mit field, name, size, sha256 |
dmg |
das notarisierte DMG |
appcast |
die appcast.xml (nur mit Sparkle) |
Dazu zwei Header:
X-BuildStation-Timestamp: 1760475600
X-BuildStation-Signature: sha256=<hex HMAC-SHA256(geheimnis, "<timestamp>.<metadata>")>
Dein Server sollte:
- die Signatur über genau den Text des Feldes
metadataprüfen, - alte Zeitstempel verwerfen, etwa älter als fünf Minuten,
- die SHA-256 der Dateien mit den Metadaten vergleichen,
- DMG und
appcast.xmlunter der Download-Adresse ablegen.
Jede Antwort mit Status 2xx gilt als Erfolg. Die Signaturprüfung in Node:
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(`${req.headers['x-buildstation-timestamp']}.${fields.metadata}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-buildstation-signature']))) {
return res.status(401).end();
}