# Amore > Amore self-publishes macOS apps outside the App Store. One command archives, code signs, notarizes, builds a DMG, uploads it, and updates the Sparkle appcast. It also sells license keys for those apps through your own Stripe account. Amore is a Mac app plus a CLI (`brew install amore`), built by Lucas Fischer. It is free to use, with an Amore+ subscription for unlimited releases, custom domains, analytics, and teams. Open-source apps get every feature for free, forever. Every documentation page and post follows in full. For a linked index instead, see https://amore.computer/llms.txt. # Get Started with Amore Source: https://amore.computer/help/get-started/ New to shipping outside the App Store? Read [how to distribute a macOS app outside the App Store](/blog/distribute-macos-app-outside-app-store/) first. There are 4 steps to get started with Amore. 1. [Download Amore](/download) (or install via Homebrew: `brew install amore`) 2. Add your app to Amore. 3. Add Sparkle to your project. 4. Publish your app. ## Video In case you prefer to watch a video, here's this guide as a [video (3:35)](https://cdn.amore.computer/videos/get-started.mp4). ## Add your app to Amore The easiest way to get started with Amore is to drag your app binary (`.app`) into Amore. You will then have to decide if you want to host your app on Amore or self-manage an S3 bucket. It's recommended to select `Amore` because the setup is simpler, you will be able to make use of advanced features and Amore will choose the best defaults for your app. If you prefer to self-manage S3 you can learn more [here](/help/s3-bucket/). ![Create App](/assets/images/help/get-started/create-app.webp) The in-app wizard will take you to through the following steps: 1. Register app with Amore / S3 setup 2. Creating Sparkle keys for signing app updates 3. Adding Sparkle to your project Please follow the instructions inside the app. ## Add Sparkle to your project For your app to be able to fetch and download updates you need to add [*Sparkle*](https://sparkle-project.org/) to your project. Sparkle will need the following 2 entries in your `Info.plist` file to fetch and verify new updates: - Update Feed URL: `SUFeedURL` - Public Key: `SUPublicEDKey` You can find the correct values for these entries in the `Sparkle` settings in Amore. ![Sparkle Settings](/assets/images/help/key-management/key-management.webp) After you added `SUFeedURL` and `SUPublicEDKey` to your `Info.plist` you need to add Sparkle as a package dependency and initialize the Sparkle updater `SPUStandardUpdaterController` class in your source code. ### Add Sparkle Package Dependency 1. In your Xcode project: File › Add Packages… 2. Enter `https://github.com/sparkle-project/Sparkle` as the package repository URL If you use Swift Package Manager (SPM) you can use: ```swift .package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"), ``` ### Initialize Sparkle Initialize the `SPUStandardUpdaterController` in your app's `@main` entry point. Here is an example for a SwiftUI lifecycle project. ```swift import SwiftUI import Sparkle @main struct AmoreApp: App { let updaterController = SPUStandardUpdaterController( startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil ) var body: some Scene { WindowGroup { ContentView() } .commands { CommandGroup(after: .appInfo) { Button("Check for Updates…") { updaterController.updater.checkForUpdates() } } } } } ``` It is recommend to also add the `Check for Updates...` menu bar button to be able to manually check for new updates. If you don't use the SwiftUI app lifecycle you can add it to your app delegate's (`NSApplicationDelegate`) `didFinishLaunchingWithOptions` method. ### Sandboxing Notes Sparkle requires [additional setup](/help/sparkle-sandboxing) when using the `App Sandbox` entitlement. For simplicity and the sake of getting started with Amore you can remove this entitlement from your app and add the `Hardened Runtime` entitlement instead. ## Publish your app After you added Sparkle and set the correct values in your `Info.plist`, you are ready to distribute your app with Amore. Now you can archive your app and select `Direct Distribution` in the Xcode Organizer to send your app to Apple for notarization. You can follow [these steps](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution#Notarize-your-app-automatically-as-part-of-the-distribution-process). Once the app is notarized you can drag and drop your app binary (`.app`) into Amore, optionally write release notes and hit publish. Congrats. You just published your first app with Amore. ![Succes](/assets/images/help/get-started/success.webp) ## Where to go from here? Whenever you want to release a new version of your app, increment the build number and go through the [release process](#publish-your-app) again. To streamline the release process you can set up the [Xcode post-archive action](/help/xcode-post-archive-action/) to let Amore take care of code signing and the notarization process after archiving automatically. If you prefer working from the terminal, the entire workflow — from setup to publishing — can be done via the [Command Line](/help/command-line/) without ever opening the app. For a complete reference project that wires up Sparkle and AmoreLicensing in a small SwiftUI app, see the [Pomodoro example](https://github.com/AmoreComputer/Pomodoro). # How to Add Licensing to Your Mac App Source: https://amore.computer/help/licensing-guide/ Amore's licensing system lets you issue, activate, and validate license keys for your macOS app. Customers purchase a license through a Stripe-powered checkout, receive their key via email, and activate it in your app. For a complete working reference, see the [Pomodoro example project](https://github.com/AmoreComputer/Pomodoro), a simple SwiftUI app that integrates both Sparkle updates and AmoreLicensing end-to-end. ## Video {% youtube "4QNPWRAxhGY", "Adding AmoreLicensing to a Swift app in Xcode", "Watch licensing go into a real Swift app, start to finish, in about three minutes." %} ## Overview There are 4 steps to set up licensing: 1. [Create a product](#create-a-product) 2. [Connect Stripe](#connect-stripe) 3. [Integrate the AmoreLicensing SDK](#integrate-amorelicensing-sdk) 4. [Share your checkout link](#share-your-checkout-link) Optionally, you can skip the checkout link and [sell from inside your app](/help/in-app-purchases/) instead. ## Create a Product A product defines what you're selling and how licenses behave — the name, device limit, and duration. You can create a product from the Licensing screen in Amore by clicking **New Product**. [Learn more about Product Settings](/help/products/) ## Connect Stripe Amore uses [Stripe](https://stripe.com) to process payments and manage subscriptions. You'll need to add your Stripe secret key and set up a webhook so Amore can issue licenses automatically when a purchase completes. [Learn more about Licensing Settings](/help/licensing/) ## Integrate AmoreLicensing SDK Add the `AmoreKit` Swift package to your project: ```swift .package(url: "https://github.com/AmoreComputer/AmoreKit", from: "0.6"), ``` Add `AmoreLicensing` as a dependency to your target: ```swift .product(name: "AmoreLicensing", package: "AmoreKit") ``` ### Initialize Create an `AmoreLicensing` instance with your public key. You can find the integration code snippet in [Licensing Settings](/help/licensing/). ```swift import AmoreLicensing let licensing = try AmoreLicensing( publicKey: "your-public-key", bundleIdentifier: "com.example.myapp" ) ``` The `bundleIdentifier` parameter defaults to `Bundle.main.bundleIdentifier` if omitted. ### Activate and Deactivate ```swift try await licensing.activate(licenseKey: "XXXX-XXXX-XXXX") try await licensing.deactivate() ``` ### Check Status `AmoreLicensing` is `@Observable`, so you can use `status` directly in SwiftUI views: ```swift switch licensing.status { case .valid(let license): // License is active case .gracePeriod(let license): // Expired but within grace window case .invalid: // Invalid or revoked case .unknown: // No license stored } ``` For the full API reference, see the [AmoreLicensing documentation](https://docs.amore.computer). For a working SwiftUI integration that gates the UI on `licensing.status`, see the [Pomodoro example](https://github.com/AmoreComputer/Pomodoro). ## Share Your Checkout Link Each product has a checkout link available in [Product Settings](/help/products/). Share this link on your website, in your app, or anywhere your customers can find it. If you use the checkout link, your customers will automatically get redirected to an Amore-hosted success page showing them their freshly issued license key and information about their purchase. In addition to the success webpage, after each successful purchase, your customers will receive their license key via email. ## Sell Inside Your App The checkout link sends customers to a browser and back again with a key to paste. `AmoreCheckout` skips that round trip: the customer pays in a sheet inside your app and the license activates itself. [Learn more about in-app purchases](/help/in-app-purchases/) # In-App Purchases with AmoreCheckout Source: https://amore.computer/help/in-app-purchases/ The classic way to sell a Mac app outside the App Store asks a lot of your customer. They leave your app for a browser, pay, wait for an email, find the license key, switch back, and paste it in. Every one of those steps is a place to lose the sale, and the ones who make it through still write to you when the email lands in spam. `AmoreCheckout` removes the detour. The customer clicks buy, pays in a sheet inside your app, and the app unlocks itself. It's part of [AmoreKit](https://github.com/AmoreComputer/AmoreKit) and bridges the two libraries you already have: `AmoreStore` fetches your products, `AmoreLicensing` activates the key. When payment completes, the key is fetched from the server and `activate(licenseKey:)` runs for you. For a complete working reference, see the [Pomodoro example project](https://github.com/AmoreComputer/Pomodoro), which integrates Sparkle updates, licensing, and in-app checkout end-to-end. ## Before You Start In-app purchases build on the licensing system, so you'll need a product and a connected Stripe account first. The [licensing guide](/help/licensing-guide/) covers both. ## Install Add the `AmoreKit` package to your project: ```swift .package(url: "https://github.com/AmoreComputer/AmoreKit", from: "0.6") ``` Add `AmoreCheckout` as a dependency to your target: ```swift .product(name: "AmoreCheckout", package: "AmoreKit") ``` Checkout runs in an embedded web view, so sandboxed apps need the outgoing-connections entitlement: ```xml com.apple.security.network.client ``` Payment can always finish in the external browser instead, which Apple Pay requires. The app activates the license either way. ## Create the Checkout Create one `AmoreCheckout` where you create your `AmoreLicensing` instance, and pass it down: ```swift let licensing = try AmoreLicensing(publicKey: "your-public-key") let checkout = AmoreCheckout(licensing: licensing) ``` You own the object. One instance serves every product, and its lifetime is yours. ## Present the Sheet Fetch your products with `AmoreStore`, then bind the one being bought. The `amoreCheckout(item:checkout:)` modifier presents checkout for whatever the binding holds: ```swift import AmoreCheckout import SwiftUI import struct AmoreStore.Product struct PaywallView: View { let checkout: AmoreCheckout let products: [Product] @State private var buying: Product? var body: some View { VStack { ForEach(products) { product in Button("Buy \(product.name)") { buying = product } } } .amoreCheckout(item: $buying, checkout: checkout) } } ``` That's the whole integration. When payment completes, the license is activated on this device and `licensing.status` flips to `.valid`, so any UI you gate on it unlocks reactively. ## Handle the Result Pass `onResult` to receive the outcome of every attempt, including retries after a failure: ```swift .amoreCheckout(item: $buying, checkout: checkout) { result in switch result { case .completed(let license, let licenseKey): print("Unlocked \(license.product.name) with \(licenseKey)") case .cancelled: break case .failed(.activationFailed(let licenseKey, _)): print("Activate manually with \(licenseKey)") case .failed(let error): print(error.localizedDescription) } } ``` The sheet already shows the license key on success, and shows it with a copy button if activation fails after payment, so nobody is ever left having paid without a key. The result carries it for your own UI too, and the key is never persisted beyond the flow. ## Recover Interrupted Purchases If the app quits mid-checkout after the customer paid, resolve the purchase once at launch: ```swift .task { await checkout.recoverPendingPurchase() } ``` Starting checkout guards the same interrupted purchase, so a customer cannot be charged twice by buying again before recovery runs. A session that already completed activates its license, one still processing is polled to its result, and an open session for the same product reopens where it left off. If the session can't be checked at all, the flow fails rather than charging again, and retrying is safe. ## Customize It The sheet is a thin layer over `AmoreCheckout`, which is UI-independent. Everything below drives the same object and activates the license the same way. ### Your Own Success Screen Build one from the same `CheckoutCompletion` and call `onDone` to close the sheet: ```swift .amoreCheckout(item: $buying, checkout: checkout, completedView: { completion in VStack { Text("Welcome to \(completion.product.name)!") Button("Start") { completion.onDone?() } } }) ``` ### Your Own Container Embed `AmoreCheckoutView` anywhere and close it from `onDone`: ```swift AmoreCheckoutView( checkout: checkout, product: product, onDone: { isPresented = false } ) ``` The view's lifetime drives the flow, so removing it from the hierarchy cancels a checkout in progress. ### Your Own Paywall `AmoreCheckout` is `@Observable`, so a paywall built from no supplied views at all can read `state` directly: ```swift switch checkout.state { case .idle, .preparing: ProgressView() case .awaitingPayment(let url): Link("Pay", destination: url) case .activating: Text("Activating…") case .completed(let license, _): Text("Unlocked \(license.product.name)") case .failed(let error): Text(error.localizedDescription) } ``` ### Browser Only To skip the embedded web view and hand the URL straight to the browser, observe `checkoutURL` and open it. The license still activates in the app when payment clears. ## Next Steps - [Custom Checkout UI](https://docs.amore.computer/documentation/amorecheckout/custom-checkout-ui) for the full customization reference - [AmoreCheckout API documentation](https://docs.amore.computer/documentation/amorecheckout) - [Pomodoro](https://github.com/AmoreComputer/Pomodoro), the open-source demo app with the whole flow - [Licensing guide](/help/licensing-guide/) if you haven't set up products and Stripe yet # Sparkle 2 Tutorial: Auto-Updates for a SwiftUI Mac App Source: https://amore.computer/help/sparkle/ [Sparkle](https://sparkle-project.org/) is the standard framework for updating Mac apps that ship outside the App Store. This tutorial adds Sparkle 2 to a SwiftUI app, from the package to the first tested update. Each step shows the manual way and, where it applies, what Amore does for you. For the full picture of signing, notarization, and packaging, see [how to distribute a macOS app outside the App Store](/blog/distribute-macos-app-outside-app-store/). You need: - an app that you sign with a Developer ID certificate and notarize, - an HTTPS server for the appcast and the update archives, and - an incrementing build number (`CFBundleVersion`) for every release. Sparkle compares versions with the build number, not with the marketing version. ## 1. Add the Sparkle package 1. In Xcode, choose File › Add Package Dependencies… 2. Enter `https://github.com/sparkle-project/Sparkle` and add the `Sparkle` library to your app target. In a `Package.swift`: ```swift .package(url: "https://github.com/sparkle-project/Sparkle", from: "2.10.1"), ``` Sparkle's command-line tools (`generate_keys`, `sign_update`, `generate_appcast`) come with the package. In Xcode's project navigator, right-click the Sparkle package and choose Show in Finder. The tools are in `../artifacts/sparkle/Sparkle/bin/`. ## 2. Generate your EdDSA keys Sparkle verifies every update with an EdDSA (ed25519) signature. Generate the key pair once: ```sh ./bin/generate_keys ``` The tool saves the private key in your login keychain and prints the public key. Run it again at any time to print the public key. The private key signs all future updates, so back it up. If you lose it, you must follow Sparkle's key rotation rules to ship a new one. Do not commit it to your repository. **With Amore:** Amore generates the key pair, or imports the one you already have. See [Key Management](/help/key-management/). ## 3. Add SUFeedURL and SUPublicEDKey to Info.plist Sparkle needs two `Info.plist` keys: ```xml SUFeedURL https://example.com/appcast.xml SUPublicEDKey your-base64-public-key ``` - `SUFeedURL` is the HTTPS URL of your appcast. App Transport Security blocks plain HTTP. - `SUPublicEDKey` is the public key from step 2. In new Xcode projects, add them in the target's Info tab. **With Amore:** copy both values from the Sparkle settings in Amore. ![Sparkle settings in Amore](/assets/images/help/key-management/key-management.webp) ## 4. Start the updater in SwiftUI Create one `SPUStandardUpdaterController` for the lifetime of the app, and add a "Check for Updates…" menu item. This follows the sample in Sparkle's documentation. The view model disables the menu item while a check is not possible: ```swift import SwiftUI import Sparkle final class CheckForUpdatesViewModel: ObservableObject { @Published var canCheckForUpdates = false init(updater: SPUUpdater) { updater.publisher(for: \.canCheckForUpdates) .assign(to: &$canCheckForUpdates) } } struct CheckForUpdatesView: View { @ObservedObject private var viewModel: CheckForUpdatesViewModel private let updater: SPUUpdater init(updater: SPUUpdater) { self.updater = updater self.viewModel = CheckForUpdatesViewModel(updater: updater) } var body: some View { Button("Check for Updates…", action: updater.checkForUpdates) .disabled(!viewModel.canCheckForUpdates) } } @main struct MyApp: App { private let updaterController = SPUStandardUpdaterController( startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil ) var body: some Scene { WindowGroup { ContentView() } .commands { CommandGroup(after: .appInfo) { CheckForUpdatesView(updater: updaterController.updater) } } } } ``` If you don't use the SwiftUI app lifecycle, create the `SPUStandardUpdaterController` in your app delegate's (`NSApplicationDelegate`) `init()`, as Sparkle's programmatic setup sample does. By default, Sparkle asks the user for permission to check automatically on the second launch. If the user agrees, Sparkle checks in the background once every 24 hours. ## 5. Sandboxed apps Sparkle needs additional setup when your app uses the `App Sandbox` entitlement. Follow [Sparkle Sandboxing](/help/sparkle-sandboxing/). If you don't need the sandbox, you can remove the entitlement to get started. Every app that you distribute with Developer ID also needs the `Hardened Runtime` capability for notarization. ## 6. Build, sign, and notarize the update Archive the app in Xcode (Product › Archive), then choose Distribute App with the Developer ID method. This also signs Sparkle's helper tools correctly. Notarize the result and package it: - **DMG:** add an `/Applications` shortcut so users copy the app out of the disk image. - **ZIP:** put only the app inside. Sparkle can update from DMG, ZIP, tar archives, Apple Archives, and installer packages. If notarization fails, see [Notarization Errors](/help/notarization-errors/). **With Amore:** one command archives, signs, notarizes, and builds the DMG. See [DMG creation](/help/dmg-creation/) and the [CLI](/help/command-line/). ## 7. Publish the appcast The appcast is an RSS feed that tells Sparkle about new versions. Put all your update archives in one folder and run: ```sh ./bin/generate_appcast /path/to/updates/ ``` `generate_appcast` signs each archive with your private key, writes the appcast with the file name from `SUFeedURL`, and creates delta updates for faster downloads. If an `.html` file has the same name as an archive, it becomes the release notes. A `.md` file also works with Sparkle 2.9 or later on macOS 12 or later. Upload the archives, the delta files, and the appcast to your server. **With Amore:** `amore release` signs the update, writes the appcast, and uploads everything. Amore hosts the appcast on HTTPS, or on your own [S3 bucket](/help/s3-bucket/) or [custom domain](/help/custom-domain/). ## 8. Test the update 1. Install an older build of your app. To test quickly, lower the `CFBundleVersion` of a development build for one run. 2. Launch it, quit, and launch it again. Sparkle asks for permission on the second launch. 3. Choose "Check for Updates…" in the app menu. To test an automatic check without waiting 24 hours, clear the last check time before you launch the app: ```sh defaults delete com.example.MyApp SULastCheckTime ``` Sparkle logs every step. Watch the log while you test: ```sh log stream --predicate 'subsystem == "org.sparkle-project.Sparkle"' ``` ## Troubleshooting The most common first error is this one: ``` Update Error! An error occurred in retrieving update information. Please try again later. ``` ![Update Error! - An error occurred in retrieving update information. Please try again later.](/assets/images/help/sparkle/sparkle-update-error.webp) Sparkle could not download the appcast: check `SUFeedURL`, use HTTPS, and in a sandboxed app check network access. For this and every other Sparkle alert, see [Sparkle Update Errors](/help/sparkle-errors/). ## Sources - [Sparkle documentation: Basic setup](https://sparkle-project.org/documentation/) - [Sparkle documentation: Programmatic setup (SwiftUI)](https://sparkle-project.org/documentation/programmatic-setup/) - [Sparkle releases](https://github.com/sparkle-project/Sparkle/releases) # Sparkle Update Errors and How to Fix Them Source: https://amore.computer/help/sparkle-errors/ [Sparkle](/help/sparkle/) shows short alerts like "Update Error!" that hide the real cause. This page lists the Sparkle 2 errors developers hit most, what triggers each one in Sparkle's source, and how to fix it. Every error text is quoted from Sparkle 2. It is part of the guide to [distributing a Mac app outside the App Store](/blog/distribute-macos-app-outside-app-store/). Sparkle shows an error alert when the user starts the check, for example with "Check for Updates…", or after Sparkle shows an update to the user. If an automatic check fails before that, the error goes to the log only. ## Read Sparkle's log first The alert only shows a summary. Sparkle logs each failed update with the full chain of underlying errors. Network errors include the URL. Stream the log in Terminal, then trigger the update check: ```sh log stream --predicate 'subsystem == "org.sparkle-project.Sparkle"' ``` In Console.app, filter on `subsystem:org.sparkle-project.Sparkle`. Sparkle's helper processes (`Autoupdate`, `Installer`, `Downloader`, `Updater`) log to the same subsystem. The commands below use Sparkle's tools (`generate_keys`, `sign_update`, `generate_appcast`) from the `bin` folder of the Sparkle download. ## An error occurred in retrieving update information. Sparkle could not download your appcast. The full alert is "An error occurred in retrieving update information. Please try again later." Common causes: 1. **Wrong feed URL.** A wrong path returns 404. A wrong host fails the DNS lookup. Open the exact URL from your `Info.plist` in a browser. If the app ever called the deprecated `setFeedURL:`, a URL stored in user defaults overrides `SUFeedURL`, and Sparkle logs a warning. 2. **HTTP instead of HTTPS.** App Transport Security blocks plain HTTP, and the log shows error -1022. When the updater starts, Sparkle also logs that the feed URL may need to change to use HTTPS. Serve the feed over HTTPS. 3. **A sandboxed app without network access.** A sandboxed app needs the `com.apple.security.network.client` entitlement, or Sparkle's downloader service, to fetch the feed. See [Sparkle sandboxing](/help/sparkle-sandboxing/). 4. **A server error or a bad TLS setup.** Sparkle treats any HTTP status of 400 or higher as an error, and the log shows the status code. A TLS failure shows an `NSURLErrorDomain` code such as -1200. ## An error occurred while parsing the update feed. Sparkle downloaded the feed but cannot read it. Either the XML is not valid, or one item is not valid. One bad item rejects the whole feed. The log names the problem, for example an XML parser error for an HTML entity such as `·`, `No enclosure in feed item`, or `Feed item lacks sparkle:version element`. Also check that the feed URL returns XML and not an HTML page, such as a login page or a GitHub "blob" page instead of the raw file. Generate the appcast with `generate_appcast` instead of editing it by hand, and check it with any XML validator. ## The update is improperly signed and could not be validated. The full alert is "The update is improperly signed and could not be validated. Please try again later or contact the app developer." Sparkle downloaded the update, but the EdDSA signature or the code signature does not match. The log usually says: ``` EdDSA signature does not match. Data of the update being checked is different than data that has been signed, or the public key and the private key are not from the same set. ``` Sparkle often adds a hint after this line, for example that the expected content length differs from the downloaded file length. That points to cause 1. Common causes: 1. **The server sends a different file than the one you signed.** A CDN or a cache serves an old archive, or you uploaded the archive again after signing. 2. **The private key does not match `SUPublicEDKey`.** You signed with a key from another Mac, another keychain account, or a new key pair. If you use a custom account, pass `--account ` to `generate_keys`, `sign_update`, and `generate_appcast`. 3. **The item has no `sparkle:edSignature`.** The log says `The app has an EdDSA public key, but there is no EdDSA signature in the update, so the update will be rejected.` This often comes from cause 2: when the keys do not match, `generate_appcast` prints a warning but still writes the item without a signature and exits with success. 4. **You changed both keys in one release.** Sparkle lets you change the EdDSA key or the Developer ID certificate in a release, but not both. 5. **The archive has no matching app.** The archive must contain an app with the same file name as the installed app, or with the same bundle identifier. The log then says `No suitable install is found in the update. The update will be rejected.` To find the cause, compare your public key with the one in the app, then check the file that your server actually sends: ```sh ./bin/generate_keys -p curl -L -o downloaded.zip "https://example.com/MyApp-2.0.zip" ./bin/sign_update --verify downloaded.zip "" ``` `sign_update --verify` prints nothing if the signature is valid. It checks against your private key, not against the app, so the first command must print the same key as `SUPublicEDKey` in the app. If the verification fails, sign and generate the appcast again, upload both, and clear your CDN cache. Do not generate a new key pair to fix this. If you must change keys, follow Sparkle's key rotation rules: in one release, change the EdDSA key or the Developer ID certificate, not both. Sign the release that changes the EdDSA key with the new private key. See [Key Management](/help/key-management/). If you use `SURequireSignedFeed`, a similar message says "The update feed is improperly signed". Run `generate_appcast` or `sign_update` on the appcast again after each manual edit. ## An error occurred while downloading the update. Sparkle found the update but could not download the archive in the `enclosure` URL. Check that the URL returns the file with status 200, uses HTTPS, and is reachable without a login. On S3 or a CDN, a 403 usually means the object is private or a presigned URL expired. If the log mentions the downloader service, see [Sparkle sandboxing](/help/sparkle-sandboxing/): apps that are not sandboxed, or that have the `com.apple.security.network.client` entitlement, should not enable `SUEnableDownloaderService`. ## An error occurred while extracting the archive. Sparkle downloaded the update but could not unpack it. Sparkle supports ZIP, DMG, tar (`.tar`, `.tar.gz`, `.tar.bz2`, `.tar.xz`), Apple Archive, and `.pkg`. It picks the format from the file extension of the downloaded file name. Check that: - the file at the enclosure URL is the archive, not an HTML error page, - the server sends a file name with the correct extension. A URL with no extension, or a wrong `Content-Disposition` header, gives `No valid unarchiver was found` in the log, - the DMG mounts when you open it, and - for `.aar` or `.yaa` archives, the *installed* app has `SUVerifyUpdateBeforeExtraction` set to `YES` and an `SUPublicEDKey`. Setting it only in the new release does not help. If a delta update fails to extract, Sparkle uses the full archive without an alert. ## An error occurred while launching the installer. Sparkle could not start its installer. The log says `Error: Failed to gain authorization required to update target`. The line before it gives the cause: - `Failed to submit installer job`: a sandboxed app cannot start the installer. Set `SUEnableInstallerLauncherService` to `YES` in `Info.plist` and follow [Sparkle sandboxing](/help/sparkle-sandboxing/). - `InstallerLauncher failed to create bundle`: the app was signed with `codesign --deep`, so the helpers got the app's sandbox entitlements. Do not use `--deep`. If you do not use Xcode's Archive and Export, sign `Installer.xpc` and the other helpers yourself. See [Sparkle helpers](/help/notarization-errors/#sparkle-helpers-and-xpc-services). - `Failed copying system domain rights`: the update needs administrator rights, for example because another user owns the app, and macOS did not grant them. Related installer messages: - **"An error occurred while connecting to the installer."** `SUEnableInstallerLauncherService` is set in an app that is not sandboxed, or `Installer.xpc` lost its executable permission. - **"An error occurred while running the updater."** In a sandboxed app, the mach-lookup exception is usually missing. Other causes are helpers that lost their executable permission, or helpers with a different team ID than the app. To add the exception, add `com.apple.security.temporary-exception.mach-lookup.global-name` with the values `$(PRODUCT_BUNDLE_IDENTIFIER)-spks` and `$(PRODUCT_BUNDLE_IDENTIFIER)-spki` to your entitlements. - **"An error occurred while starting the installer."** The installer did not start within 7 seconds. The log says `Timeout: Installer never started archive extraction`. ## You’re up to date! (but an update exists) Sparkle found no update that applies to this installation. Check these, in this order: 1. **`CFBundleVersion`.** Sparkle compares `sparkle:version` in the appcast with the build number (`CFBundleVersion`), not with the marketing version. Increase the build number for every release. 2. **Channels.** An item with `sparkle:channel` is only offered if your updater delegate returns that channel in `allowedChannelsForUpdater:`. 3. **Minimum update version.** `sparkle:minimumUpdateVersion` is higher than the installed version. 4. **An old feed URL.** If the app ever called the deprecated `setFeedURL:`, that URL stays in user defaults and overrides `SUFeedURL`. Sparkle logs a warning about it. 5. **A cached appcast.** Your CDN serves an old copy of the appcast. Open the feed URL and check that the new item is there. Phased rollout does not cause this alert, because a manual check ignores it. It only delays automatic checks, so an automatic check can find no update while a manual check finds one. The `generate_appcast` options that control these are `--channel`, `--minimum-update-version`, and `--phased-rollout-interval`. ## Your macOS version is too old The update requires a newer macOS than the user has. The alert says the version is available but "your macOS version is too old to install it". `generate_appcast` copies `LSMinimumSystemVersion` from your app into `sparkle:minimumSystemVersion`. Related alerts are "Your macOS version is too new" (from `sparkle:maximumSystemVersion`) and "Your Mac is too old", which appears on Intel Macs when the item sets `sparkle:hardwareRequirements` to `arm64` or requires macOS 27 or later. `generate_appcast` adds `arm64` when your app has no Intel code. ## MyApp can’t be updated if it’s running from the location it was downloaded to. macOS runs the app from a temporary, randomized location (App Translocation) because the user opened the downloaded app without moving it first, for example from the Downloads folder. The alert asks the user to move the app into the Applications folder and relaunch it. A similar alert, "MyApp can’t be updated because it was opened from a read-only or a temporary location.", usually appears when the user runs the app directly from the mounted DMG. To prevent both, put an `/Applications` shortcut in your DMG so users drag the app there. See [DMG creation](/help/dmg-creation/). ## Unable to Check For Updates The alert says "The updater failed to start." Sparkle refused to start, usually because of the EdDSA public key: - **`SUPublicEDKey` is missing** and the feed uses HTTP or the app is not code signed. Sparkle logs `For security reasons, updates need to be signed with an EdDSA key`. - **`SUPublicEDKey` is not valid.** Sparkle logs `The EdDSA public key is not valid`. Copy the key again from `./bin/generate_keys -p`. - **`SURequireSignedFeed` without `SUVerifyUpdateBeforeExtraction`.** Set both to `YES`. - **An XPC service is enabled but missing.** `SUEnableInstallerLauncherService` or `SUEnableDownloaderService` is `YES`, but the service is not in the Sparkle framework, or you copied it into the app bundle yourself. See [Sparkle sandboxing](/help/sparkle-sandboxing/). If the key in the app does not match the key in your keychain, `generate_appcast` prints `Warning: SUPublicEDKey in the app ... does not match key EdDSA in the Keychain.` It still writes the appcast, but without `sparkle:edSignature` for that item. Fix this before you publish. A missing `SUFeedURL` does not stop the updater from starting. A manual check then shows "Update Error!" and the log says `You must specify the URL of the appcast`. To use your private key in CI, export it once, store the contents of `private-key.txt` as a secret, and pass it to `generate_appcast`. Use `--ed-key-file`, not `-f`: in `generate_appcast`, `-f` is the old DSA key. ```sh ./bin/generate_keys -x private-key.txt echo "$SPARKLE_PRIVATE_KEY" | ./bin/generate_appcast --ed-key-file - ./updates ``` ## Skip the manual steps Amore generates the EdDSA keys, signs every update, writes the appcast, and hosts it on HTTPS. Start with [Get Started](/help/get-started/), or see [Key Management](/help/key-management/) to import the key you already use. # Host Sparkle Updates on Your Own S3 Bucket Source: https://amore.computer/help/s3-bucket/ ![Create App](/assets/images/help/s3/s3.webp) Amore intends not to lock you in and to make migration easy.  If you want to stay in control or already use an existing S3 bucket, you can use Amore with any S3-compatible data storage provider of your choice. If you already have an existing S3 bucket with updates and `appcast.xml`, Amore will be able to read your `appcast.xml` and will leave all the other updates alone. When you publish a new release via Amore, the new release will get added to the beginning of the `appcast.xml`. To create a persistent download link for your users, Amore will automatically copy the latest release to `/{Path Prefix}/{Product Name}.zip|dmg`, depending on your preferences. Amore will show the persistent download link to your latest release in `General`. Once everything is setup correctly you can manage releases previously added to `appcast.xml` from within Amore and use features like beta channel, phased rollouts and critical updates. You can use Amore to update the release notes of all of your releases. ## Setup When creating a new app choose `Self-Managed S3`, which will prompt you to configure an S3 bucket. You need to set your access key id and secret access key. In addition, Amore needs the bucket name, region an optionally the API endpoint you want to use in case you don't use AWS. On the top right of the S3 Configuration pane you can see if your credentials and configuration is valid. If everything works you should see a green seal icon. ## Advanced Options | Option | Description | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Path Prefix** | Optional folder path within the bucket. Will also be applied to Appcast Path. | | **Public Base URL** | The public URL where files are accessible (e.g., CloudFront distribution or S3 bucket URL). | | **Appcast Path** | The path to the `appcast.xml` file within the bucket. Useful if you want to use an existing setup. Leave empty otherwise. Will respect Path Prefix if set. | ## Cloudflare R2 If you want to use Amore with Cloudflare R2 you need to set `Region` to `auto`. # Code Signing and Notarization Source: https://amore.computer/help/codesigning/ Every app you [distribute outside the App Store](/blog/distribute-macos-app-outside-app-store/) must be signed with a Developer ID certificate and notarized. Amore's codesigning settings are required for advanced but **optional** features like [automatic DMG creation](/help/dmg-creation/) and the [Xcode post-archive action](/help/xcode-post-archive-action). ## Codesign Identity If you want to use the [Xcode post-archive action](/help/xcode-post-archive-action/) or want to be able to drag and drop unsigned binaries into Amore you need to select a codesign identity in settings. Amore will use the selected identity to sign your app for distribution. You can manage available codesign identities in Xcode. If you haven't previously created a code signing identity, you can create one by following [these steps](https://developer.apple.com/documentation/xcode/sharing-your-teams-signing-certificates#Create-a-new-code-signing-identity): 1. Open Xcode. 2. Choose Xcode > Settings. 3. In the toolbar, click Accounts. 4. Select your Apple Account from the list of accounts. 5. Select the team to create the code signing identity for from the list of your Apple Account teams. 6. Click Manage Certificates. 7. In the lower-left corner of the signing certificates sheet, click the Add button (+) and choose `Developer ID Application` from the pop-up menu. ## Notarization Keychain Profile If you want to use [Xcode post-archive action](/help/xcode-post-archive-action/) or want Amore to automatically wrap your app binary in a [DMG file](/help/dmg-creation/), you need to setup a notarization keychain profile. Amore will then use the selected keychain profile to notarize your app. You only have to set this up once. Run the following command in your shell and follow the instructions. ```sh xcrun notarytool store-credentials "${YOUR_KEYCHAIN_PROFILE}" ``` After you are done, paste what you used as `${YOUR_KEYCHAIN_PROFILE}` into the Amore notarization keychain profile under `Codesigning` → `Notarization Keychain Profile`. [Learn More about Apple Notarization](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution). If Apple rejects your app, see [Notarization Errors](/help/notarization-errors/). # Notarization Errors and How to Fix Them Source: https://amore.computer/help/notarization-errors/ Apple's notary service rejects a build with a status of `Invalid` and a short reason in its log. This page lists the errors developers hit most, what causes each one, and how to fix it. Each error text is quoted from Apple's documentation or from Apple's tools. Notarization is one step of [distributing a macOS app outside the App Store](/blog/distribute-macos-app-outside-app-store/). ## Read the notarization log first `notarytool` only tells you that a submission is `Invalid`. The reason is in the log: ```sh xcrun notarytool submit MyApp.dmg --keychain-profile "notary" --wait xcrun notarytool log --keychain-profile "notary" notary-log.json ``` The log is JSON. Each entry in `issues` has a `message`, the `path` of the file it is about, and a `severity`. Apple recommends that you [always read the log](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow), even when notarization succeeds, because it also lists warnings. To see all your submissions and their status: ```sh xcrun notarytool history --keychain-profile "notary" ``` ## The signature of the binary is invalid. Something changed the bundle after you signed it. Apple also lists a missing signature as a cause, but for a Mach-O file with no signature, notary logs usually say [The binary is not signed.](#the-binary-is-not-signed) instead. Common causes are a build script that copies a file into the app after signing, and a framework that you signed before you modified it. Check the signature: ```sh codesign -vvv --deep --strict MyApp.app ``` For an installer package, use `pkgutil --check-signature MyApp.pkg`. Sign again after the last change to the bundle. ## The binary is not signed. The file at `path` is a Mach-O binary that has no code signature. Usually this is code that you added to the bundle yourself: a command-line tool, a `.dylib`, a `.so` or `.node` module, or a bundled Python or Node runtime. The notary service also looks inside ZIP and JAR files in the bundle. The same `path` usually also gets the secure timestamp and hardened runtime errors. Look at where the file is. `codesign` does not sign an app that has unsigned code in `Contents/MacOS` or `Contents/Frameworks`. But in `Contents/Resources`, `codesign` seals the file as data. The app signs without an error, and `codesign --deep --strict` passes. Put tools in `Contents/MacOS` or `Contents/Helpers`, and libraries in `Contents/Frameworks`. Keep only scripts and data in `Contents/Resources`. See [Placing content in a bundle](https://developer.apple.com/documentation/bundleresources/placing-content-in-a-bundle). To list the unsigned Mach-O files: ```sh find MyApp.app -type f -exec sh -c 'file -b "$1" | grep -q Mach-O && ! codesign -d "$1" 2>/dev/null && echo "$1"' _ {} \; ``` Sign each file with your Developer ID, `-o runtime`, and `--timestamp`. Start with the innermost files and sign the app last, as in [Sparkle helpers](#sparkle-helpers-and-xpc-services). For code in a ZIP or JAR file, sign it before you pack the archive. The command above does not look inside archives. ## The binary is not signed with a valid Developer ID certificate. You signed with the wrong certificate type, for example Apple Development, Mac App Distribution, or a self-signed certificate. Sign apps and DMGs with **Developer ID Application**. Sign installer packages with **Developer ID Installer**. ```sh codesign -dvv MyApp.app 2>&1 | grep Authority ``` The first `Authority` line must start with `Developer ID Application:`. If your certificate comes from the old Developer ID authority, also read [Developer ID Certificates Expire February 1, 2027](/blog/developer-id-certificate-expires-2027/). ## The signature does not include a secure timestamp. A manual `codesign` step or a custom export left out the timestamp. Add `--timestamp` to every `codesign` command, or to `OTHER_CODE_SIGN_FLAGS` in Xcode. The Mac that signs needs internet access to `timestamp.apple.com`. ```sh codesign -dvv MyApp.app 2>&1 | grep -E "Timestamp|Signed Time" ``` A `Timestamp=` line means the signature has a secure timestamp. A `Signed Time=` line means it does not. ## The executable does not have the hardened runtime enabled. Turn on the **Hardened Runtime** capability in Xcode, or add `--options=runtime` to your `codesign` command. Every executable in the bundle needs it, including helpers and XPC services inside frameworks. See [Sparkle helpers](#sparkle-helpers-and-xpc-services) below. ## The executable requests the com.apple.security.get-task-allow entitlement. This entitlement allows debuggers to attach, and the notary service rejects it. Xcode adds it to every build by default (`CODE_SIGN_INJECT_BASE_ENTITLEMENTS=YES`). Archive and export remove it. A custom workflow that ships the build product directly keeps it. Archive and export from Xcode, or set that build setting to `NO` for release builds. ## The binary uses an SDK older than the 10.9 SDK. Build against the macOS 10.9 SDK or later. This does not drop support for older systems: the deployment target, not the SDK, decides which macOS versions your app runs on. ## Embedded entitlements are invalid Apple describes this as an error "like" `Embedded entitlements are invalid: syntax error near line 1`. The entitlements in the signature are malformed. Usually they are a binary property list, or the file is not plain ASCII, for example because it starts with a byte order mark. Check for `bplist00` in the output of: ```sh codesign -d --entitlements :- MyApp.app ``` Current versions of `codesign` warn that the `:` form is deprecated, but it still works. The `--xml` replacement always prints XML, so it cannot show `bplist00`. Convert the file to XML, check it, and sign again: ```sh plutil -convert xml1 MyApp.entitlements plutil -lint MyApp.entitlements ``` ## Notarization is stuck "In Progress" Apple says notarization completes for most software within 5 minutes, and for 98 percent of software within 15 minutes. Some uploads are held for additional analysis. This happens most often to teams that notarize for the first time. - **Wait.** Apple's engineers say the delay usually clears itself in a day or two, and that delays become less frequent as the service learns your app. - **There is no cancel.** `notarytool` has no cancel command. - **`--timeout` only stops waiting.** The notary service continues to process the submission. - **Escalate after a week.** If all your requests stay `In Progress` for more than a week, post in the [Apple Developer Forums](https://developer.apple.com/forums/) under Code Signing > Notarization, with the submission UUID and the date of the first stuck request. Apple's [System Status](https://developer.apple.com/system-status/) page has a row for the Developer ID Notary Service. Apple also asks you to limit notarizations to 75 per day. ## The staple and validate action failed! Error 65. Error 65 is `EX_DATAERR`, "The ticket data is invalid." `stapler` could not get a ticket that matches the file, or it could not validate the ticket. In the first case, the log above the error usually says `Record not found` and `Could not find base64 encoded ticket in response`. Usually no ticket exists: you did not notarize the file, or notarization failed. Check that the status is `Accepted`. If it is, you staple a different file than the one you submitted. If you sign a file again after notarization, its code directory hash changes and the ticket no longer matches. Staple exactly the file that the notary service accepted. If the message says `Could not validate ticket` instead, `stapler` found the ticket, but trust evaluation failed. A common cause is changed trust settings on Apple's root certificates on this Mac. List them with `security dump-trust-settings` and `security dump-trust-settings -d`, and remove the custom trust settings in Keychain Access. Other `stapler` exit codes. The man page names them from sysexits(3): | Error | Meaning | |-------|---------| | 66 | The path cannot be found, is not code-signed, or is not a supported file format. With `validate`, the stapled ticket is missing or invalid. | | 68 | The path was not notarized, or the ticket service returned an unexpected response. | | 73 | The ticket could not be written to disk. The folder that contains the file must be writable. | | 77 | The ticket service revoked the ticket. | You cannot staple a ZIP file. `stapler` needs network access to CloudKit over port 443. Signing a file again invalidates a ticket that is already stapled. ## HTTP status code: 401. Invalid credentials. The full message starts `Error: HTTP status code: 401. Invalid credentials. Username or password is incorrect.` Check these, in this order: 1. You use an **app-specific password** from appleid.apple.com, not your Apple ID password. 2. The team ID is correct, and you are a member of that team. 3. With an App Store Connect API key, the key has the **Developer**, **App Manager**, or **Admin** role. Use a **Team** key: Apple's documents disagree on whether Individual keys work with `notarytool`. Do not pass `--issuer` with an Individual key, because that causes a `401 Unauthorized` response. If `notarytool` says `Ensure your team has signed the necessary legal agreements and that they are not expired.`, the Account Holder must accept the current agreements in your developer account. Test your credentials without a submission: ```sh xcrun notarytool history --keychain-profile "notary" ``` To store credentials once, with an API key: ```sh xcrun notarytool store-credentials "notary" \ --key AuthKey_ABC123.p8 --key-id ABC123 --issuer ``` For CI, see [GitHub Actions](/help/github-actions/), which uses an App Store Connect API key. ## Sparkle helpers and XPC services [Sparkle](/help/sparkle/) ships its helpers (`Autoupdate`, `Updater.app`, `Installer.xpc`, `Downloader.xpc`) with an ad-hoc signature. If you sign the app yourself, sign each helper with your Developer ID, the hardened runtime, and a timestamp. Sign from the inside out, and sign the app last. These are Sparkle's commands for Sparkle 2.6 or later, plus the `--timestamp` that notarization needs: ```sh ID="Developer ID Application: Your Name (TEAMID)" FW=MyApp.app/Contents/Frameworks/Sparkle.framework codesign -f -s "$ID" -o runtime --timestamp $FW/Versions/B/XPCServices/Installer.xpc codesign -f -s "$ID" -o runtime --timestamp --preserve-metadata=entitlements $FW/Versions/B/XPCServices/Downloader.xpc codesign -f -s "$ID" -o runtime --timestamp $FW/Versions/B/Autoupdate codesign -f -s "$ID" -o runtime --timestamp $FW/Versions/B/Updater.app codesign -f -s "$ID" -o runtime --timestamp $FW codesign -f -s "$ID" -o runtime --timestamp --entitlements MyApp.entitlements MyApp.app ``` Apple [says](https://developer.apple.com/forums/thread/129980): do not pass `--deep` to `codesign` when you sign. It is fine for verification. Sparkle's docs say that Archive and Export in Xcode signs the helpers for you, but "Code Sign on Copy" does not. ## “MyApp” is damaged and can’t be opened. Users see this Gatekeeper alert, with the suggestion "You should move it to the Trash.", when the app's signature is broken. Usually a file was added or changed after signing, or a copy changed the Unicode normalization of a file name. Use ASCII file names in the bundle. Verify the app: ```sh codesign -v -vvv --strict --deep MyApp.app ``` `a sealed resource is missing or invalid` in the output confirms the cause. Sign again after the last change, then notarize the new build. For the other causes of this alert, see [“App Is Damaged and Can’t Be Opened”: The Fix for Mac Developers](/blog/app-is-damaged-and-cant-be-opened/). ## Apple could not verify “MyApp” is free of malware Gatekeeper shows this when the app is not notarized, or when it cannot find the ticket. This happens when the ticket is not stapled and the Mac is offline, so Gatekeeper cannot get the ticket from Apple. Test the way your users get the app: download it with Safari so that it gets the quarantine flag, then run: ```sh syspolicy_check distribution MyApp.app spctl -a -t exec -vvv MyApp.app spctl -a -t open -vvv --context context:primary-signature MyApp.dmg ``` `syspolicy_check` needs macOS 14 or later. ## Skip the manual steps Amore signs and notarizes every release as part of one command. Set it up in [Code Signing and Notarization](/help/codesigning/), then release with the [CLI](/help/command-line/) or [GitHub Actions](/help/github-actions/). ## Sources - [Resolving common notarization issues](https://developer.apple.com/documentation/security/resolving-common-notarization-issues) - [Customizing the notarization workflow](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow) - [Creating distribution-signed code for macOS](https://developer.apple.com/documentation/xcode/creating-distribution-signed-code-for-the-mac) - [Placing content in a bundle](https://developer.apple.com/documentation/bundleresources/placing-content-in-a-bundle) - [Sparkle: Code signing helpers](https://sparkle-project.org/documentation/sandboxing/) - [Apple Developer Forums: notarization stuck In Progress](https://developer.apple.com/forums/thread/739751) - [Apple Developer Forums: testing a notarized product](https://developer.apple.com/forums/thread/130560) - [Apple Developer Forums: resolving Gatekeeper problems](https://developer.apple.com/forums/thread/706379) - [Apple Developer Forums: --deep considered harmful](https://developer.apple.com/forums/thread/129980) - [Apple Developer Forums: The binary is not signed](https://developer.apple.com/forums/thread/707833) - [Apple Developer Forums: the pros and cons of stapling](https://developer.apple.com/forums/thread/720093) - [Apple Developer: roles](https://developer.apple.com/help/account/access/roles/) - The `notarytool` and `stapler` man pages # Create a Notarized DMG for Your Mac App Source: https://amore.computer/help/dmg-creation/ A DMG is the standard installer format when you [distribute a Mac app outside the App Store](/blog/distribute-macos-app-outside-app-store/). Amore can automatically create DMG images during the release flow. When you release a new version of your app, Amore will package it into a DMG with a custom background and drag-to-install experience. The DMG will be code signed and notarized as part of the release process, ensuring your users can install your app without security warnings. If you drag and drop a DMG directly in Amore, it won't generate a DMG for you and will use your DMG instead. During DMG creation, Amore will launch a Finder window to correctly set the background image and resize the window. This is normal. The Finder window will automatically be closed once Amore is done creating the DMG. ![A screenshot of a automatically created DMG from Amore](/assets/images/help/dmg-creation/amore-dmg.webp) ## Video {% youtube "O8Hr8EXjyNw", "Creating a macOS DMG installer with Amore", "Watch Amore build a signed, notarized DMG installer in about two minutes." %} ## Requirements To let Amore automatically create installer DMG images for your app, you need to set up a [Codesign Identity](/help/codesigning/#codesign-identity) and [Notarization Keychain Profile](/help/codesigning/#notarization-keychain-profile) in the `Codesigning` settings. ## Setup Once you setup a [Codesign Identity](/help/codesigning/#codesign-identity) and [Notarization Keychain Profile](/help/codesigning/#notarization-keychain-profile) in the `Codesigning` settings, you will be able to enable `Create DMG Installer Image` in the `DMG Creation` settings. ### Troubleshooting Make sure you disabled the `Open folders in tabs instead of new windows` in your Finder settings for better DMG results. ## Watermark The created DMG installer image will include a `Built with amore.computer` watermark for free users. If you want to remove it, you can disable the `Show Watermark` settings after you subscribe to [Amore+](/pricing/). ## Custom Background With [Amore+](/pricing/) you can replace the generated background with your own artwork. The DMG window is always `600 × 400` points, so your image only needs to match that canvas: ![The DMG background template, with dashed placeholders marking where Finder places the app icon, the Applications folder, and their labels](/assets/images/help/dmg-creation/dmg-background-template.png) 1. [Download the template](/assets/images/help/dmg-creation/dmg-background-template.png) and open it in your design tool. The dashed boxes mark exactly where Finder places your app icon, the `Applications` folder, and their labels. 2. Design over it and export at `1200 × 800` pixels for a sharp result on Retina displays. PNG, JPG, and TIFF all work. Amore takes care of the DPI metadata for you, so a plain `1200 × 800` export at 72 DPI renders correctly. 3. Configure the exported image for your app: ```sh amore config set release dmg-background background.png -b com.example.App ``` Every following release will use your background, in the app and with `amore release`. For a single release, pass `--dmg-background ` to `amore release` or `--background ` to `amore create-dmg` instead. To return to the generated background, clear the setting: ```sh amore config set release dmg-background "" -b com.example.App ``` Images with a different aspect ratio or smaller than `600 × 400` pixels are rejected before the release starts, so you can't accidentally ship a stretched, cropped, or blurry background. ### Video {% youtube "pDLCaBaovMQ", "How to Create a Custom DMG Installer Background for Your Mac App", "Design a custom DMG background from the template and configure it in Amore." %} # Sparkle EdDSA Key Management Source: https://amore.computer/help/key-management/ Amore uses the macOS keychain to securely store your EdDSA signing keys to sign your updates for use with Sparkle. By default Amore stores and generates a separate private key for each app. **It is recommended to additionally store your private keys in a secure place, like password manager to not lose them.** You can learn more about how Amore signs each update for Sparkle [here](https://sparkle-project.org/documentation/#3-segue-for-security-concerns). ![Key Management](/assets/images/help/key-management/key-management.webp) ## Use Existing Key Pairs If you already setup Sparkle on your Mac before, Amore can automatically import your existing Sparkle private key and you don't have to do anything. If you already have a key pair you can import the private key by clicking on the `+` button in the top right corner to import your existing key. ## Generate a New Key Pair If you are setting up your first app to use with Sparkle you won't have any key pair in your keychain yet. You can create a new one to use with your apps by clicking the `Generate Keys` button in `Key Management`. # Xcode Post-Archive Action Source: https://amore.computer/help/xcode-post-archive-action/ To streamline the release process, *Amore*'s [CLI](/help/command-line/) offers a command to be used with Xcode' archive post-actions, which will automatically handle every part of the release process after you archived your project. This includes: 1. Exporting the archive 2. Codesigning your executable 3. Creating an installer DMG (optional) 4. Notarizing your app 5. Signing your app for the Sparkle updater 6. Creating a new `appcast.xml` 7. Uploading your app binary ## Video {% youtube "liOhLYlneb8", "Setting up the Amore post-archive action in Xcode", "The full setup: hit Archive in Xcode and Amore signs, notarizes, and publishes the update. About four minutes." %} ## Requirements To use the Amore's Xcode post-archive action you first need to setup a [Codesign Identity](/help/codesigning/#codesign-identity) and [Notarization Keychain Profile](/help/codesigning/#notarization-keychain-profile) in the `Codesigning` settings. You also need to have the `amore` [CLI](/help/command-line/) installed. ## Setup All you need to do to set this up is adding the following command line instructions to your archive post-actions run script: ```sh amore post-archive ``` You get there by clicking by `Menu Bar` -> `Product` -> `Scheme` -> `Edit Scheme` -> `Archive` -> `Post-actions`. ![Xcode Post Archive Action](/assets/images/help/xcode-post-archive-action/xcode.png.webp) Now, after you successfully [archive](https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases#Create-an-archive-of-your-app) your app for distribution, Amore will take over and open the Amore desktop app, which after your confirmation will take care of all the previously mentioned steps for you. # Licensing Settings Source: https://amore.computer/help/licensing/ The Licensing settings screen configures how Amore connects to [Stripe](https://stripe.com) to process license purchases for your app. ## Stripe Integration ### Secret Key Your Stripe secret key (`sk_live_...`) allows Amore to create checkout sessions and manage subscriptions on your behalf. You can find it in your [Stripe dashboard](https://dashboard.stripe.com/apikeys). If you prefer a [restricted key](https://docs.stripe.com/keys#secret-and-restricted-keys), grant these permissions: - **Checkout Sessions** — Read + Write - **Prices** — Read - **Products** — Read - **Subscriptions** — Read ### Webhook Secret The webhook signing secret (`whsec_...`) is used to verify that incoming webhook events are genuinely from Stripe. You'll get this after creating the webhook endpoint in Stripe. ### Webhook URL Amore generates a webhook URL for your app: ``` https://api.amore.computer/v1/webhooks/stripe/{bundleID} ``` Copy this URL and add it as an endpoint in your Stripe dashboard. ### Stripe Managed Payments When enabled, Stripe acts as the merchant of record for your sales. Learn more about [Stripe managed payments](https://docs.stripe.com/payments/managed-payments). ## Setting Up the Stripe Webhook 1. Go to your [Stripe dashboard](https://dashboard.stripe.com/webhooks) 2. Click **Add endpoint** 3. Paste the webhook URL from Amore 4. Select the following events: - `checkout.session.completed` - `invoice.paid` - `customer.subscription.deleted` - `customer.subscription.paused` - `customer.subscription.resumed` - `customer.subscription.updated` 5. Click **Add endpoint** 6. Copy the **signing secret** and paste it into the Webhook Secret field in Amore For more details, see the [Stripe webhook documentation](https://docs.stripe.com/webhooks). ## Integration Code After your first product is created, the Licensing screen shows a copyable code snippet with your public key and bundle identifier: ```swift let licensing = try AmoreLicensing( publicKey: "your-public-key", bundleIdentifier: "com.example.myapp" ) ``` Use this snippet to initialize the [AmoreLicensing SDK](https://docs.amore.computer) in your app. # Product Settings Source: https://amore.computer/help/products/ Products define what you're selling and how licenses behave. Each product has its own device limit, duration, and Stripe configuration. ## Create a Product Click **New Product** in the Licensing screen to create a product. You'll set three fields: - **Name** — the product name (e.g. "Pro License") - **Device Limit** — how many devices can activate a single license - **Duration** — license duration in days. Leave empty for a perpetual license. For Stripe subscriptions, this is automatically set from the billing cycle. ## Product Settings After creation, you will find your created products in the Amore sidebar in the »Products« section. The product settings screen has additional fields: ### Name The display name for your product. ### Device Limit The maximum number of devices that can be activated with a single license key. ### Duration License duration in days. An empty value means the license never expires. When linked to a Stripe subscription, the duration is automatically managed based on the subscription's billing cycle. In that case it's best to leave it empty. ### Stripe Product ID The Stripe product ID (`prod_...`) from your [Stripe dashboard](https://dashboard.stripe.com/products). This links purchases to the correct product. ### Stripe Price ID The Stripe price ID (`price_...`) used to create checkout sessions. You can find this on the product page in your [Stripe dashboard](https://dashboard.stripe.com/products). ## Checkout Link Each product has a hosted checkout link. From the product settings header, you can: - **Copy Checkout Link** — copy the URL to your clipboard - **Open Checkout In Browser** — preview the checkout page Share this link on your website, in your app, or anywhere your customers can find it. If you use the checkout link, your customers will automatically get redirected to an Amore-hosted success page showing them their freshly issued license key and information about their purchase. To keep customers in your app instead of sending them to a browser, use [in-app purchases](/help/in-app-purchases/). In addition to the success webpage, after each successful purchase, your customers will receive their license key via email. # API Keys Source: https://amore.computer/help/api-keys/ API keys let CI, scripts and the [GitHub Action](/help/github-actions/) authenticate as you without signing in interactively. Instead of your login, an automated release carries a scoped token that you create in the app and can revoke at any time. Publishing a release from CI needs exactly one of these, passed as `AMORE_TOKEN`. ## Create a key 1. Open `Amore` → `Settings…` (`⌘,`) and scroll to `API Keys`. You need to be signed in to Amore to manage keys. 2. Click `New Key`. 3. Give it a `Name` you'll recognize later, e.g. `GitHub Actions`. It's just a label. 4. Pick a `Scope` (see below). `Release` is the default and the right choice for CI. 5. Click `Create`. The secret token appears once. Copy it right then: for your security, Amore never shows it again. If you lose it, delete the key and create a new one. A token looks like `amore_` followed by a long string. Treat it like a password. ## Scopes - **`Release`**: upload releases and read your account. Best for CI. - **`Full access`**: every endpoint, including licenses and products. Give a key the least it needs. `Release` is enough to build and publish, so use it for any release pipeline. Reach for `Full access` only when a script also has to manage licenses or products. ## Use it in GitHub Actions Add the token as a repository secret named `AMORE_TOKEN` under `Settings` → `Secrets and variables` → `Actions` → `New repository secret`, then pass it into the release step: {% raw %} ```yaml amore-token: ${{ secrets.AMORE_TOKEN }} ``` {% endraw %} That's the only Amore-specific secret the action needs. See [GitHub Actions](/help/github-actions/) for the full workflow and the Apple signing secrets. ## Use it with the CLI Set `AMORE_TOKEN` in the environment and the [CLI](/help/command-line/) authenticates with it instead of your local sign-in, so there's no `amore login` step: ```sh export AMORE_TOKEN=amore_your_token_here amore release --scheme MyApp ``` This is what any CI runner or server-side script should do. When `AMORE_TOKEN` is set the CLI runs in token mode and won't read or write your login keychain. ## Manage and rotate keys The `API Keys` list shows each key's `Name`, `Scope` and `Last used`. `Last used` reads `Never` until the key's first request, so you can spot one that isn't being used. To rotate a key, create a new one, update `AMORE_TOKEN` wherever it's stored, then delete the old key. Deleting takes effect immediately: any CI or script still using that token stops working at once. Use a separate key per pipeline or machine so you can revoke one without breaking the others. # Release Mac Apps from the Command Line Source: https://amore.computer/help/command-line/ The *Amore* app ships with a powerful command line tool that makes it easy to automate & integrate the most common actions into your process. ## Installation ### Via Homebrew The quickest way to get started is [Homebrew](https://brew.sh). The cask installs Amore.app and links the `amore` command for you: ```sh brew install amore ``` ### Via Terminal If you installed Amore.app another way, you can install the CLI from the terminal without opening the app: ```sh /Applications/Amore.app/Contents/MacOS/AmoreCLI install ``` This creates a symlink at `/usr/local/bin/amore`. If you get a permission error, run with `sudo`: ```sh sudo /Applications/Amore.app/Contents/MacOS/AmoreCLI install ``` ### Via the App Alternatively, navigate to the Amore app `Command Line` settings and click `Install`. You will be prompted to enter your password so Amore can create the symlink at `/usr/local/bin/amore`. --- After installation, you can use the CLI without ever opening the app. A typical CLI-only workflow looks like this: ```sh amore login # Sign in to your account amore setup MyApp.app # Set up a new app amore release --scheme MyApp # Build, sign, notarize, and publish ``` To learn more about the `amore` cli and its capabilities use: ```sh amore --help ``` ## Release {% youtube "pmNKBp3VhFM", "Releasing a macOS app with one amore release command", "From Xcode project to published, notarized app with a single command. Under three minutes." %} If you prefer to use the CLI, instead of Amore's GUI you can use the `amore` cli to release new versions of your app. This command is able to release .app bundles, .dmg files, .zip archives, Xcode archives and archiving directly from Xcode projects/workspaces. In a single command `amore release` takes care of: 1. Archiving your app 2. Exporting the archive 3. Codesigning your executable 4. Creating an [installer DMG](/help/dmg-creation) (optional) 5. Notarizing your app 6. Signing your app for the Sparkle updater 7. Creating a new `appcast.xml` 8. Uploading your app binary for distribution ```sh amore release /path/to/App.xcodeproj --scheme MyApp # You can omit the path when executing `amore` from inside your project folder amore release --scheme MyApp ``` Alternatively you can use the `amore release` command to release your already built app or DMG image. ```sh amore release /path/to/App.app amore release /path/to/App.dmg ``` ## Coding Agents If you let Claude Code or another coding agent drive your releases, install the [Amore agent skill](/help/agent-skill/). It gives the agent the CLI plus Amore's documentation as context, so it follows the documented flows instead of inventing them. # Amore Agent Skill for Claude Code Source: https://amore.computer/help/agent-skill/ Everything you can do in the Amore app you can do from the [command line](/help/command-line/). The Amore agent skill is the AI-friendly companion to the CLI: a Claude Code plugin that lets coding agents interact with Amore using the `amore` command, with Amore's documentation loaded as guidance. To set it up, copy this prompt and paste it into Claude Code, Cursor, or any coding agent working in your project folder: {% include "ai-setup.html", label: "Copy setup prompt" %} ## Install Install the skill from the [amore-skill repository on GitHub](https://github.com/AmoreComputer/amore-skill). Follow the README for the exact `claude` install command. ## Use Once installed, you can ask Claude Code and other agents to release a new version, set up licensing, or wire up Sparkle, and the agent will follow Amore's documented workflows instead of guessing. ## Docs as markdown Every page under `amore.computer/help` is available as clean markdown: add `.md` to the URL, for example [amore.computer/help/get-started.md](/help/get-started.md). Point your agent at these when it needs deeper detail on a specific topic. # Custom Domain for Your Sparkle Update Feed Source: https://amore.computer/help/custom-domain/ Amore allows you to use a custom domain for your app's `appcast.xml` and download link. Once set up, your users will fetch updates and downloads from your domain, not Amore's. **It's recommended to set up a custom domain for your app to prevent vendor lock-in.** ## Requirements - Amore+ Subscription - App using the Amore storage backend - A domain you can use ## Setup To set up a custom domain for your app navigate to the `General` settings in the sidebar, enter a domain in the `Custom Domain` section and click `Save`. For example, you want to use `updates.example.com` as custom domain for your app. Enter `updates.example.com` in the Amore custom domain settings and add the following record in your domain's DNS settings: | DNS Record Type | Name | Target | | --------------- | ------- | ------------------ | | CNAME | updates | api.amore.computer | After you set this up, Amore will periodically check if you set the correct DNS records in the upper right corner of the custom domain settings pane. Once everything is set up, you should see a green checkmark. # Why Your Mac App Icon Looks Too Big (and How to Fix It) Source: https://amore.computer/help/why-your-mac-app-icon-looks-too-big/ Most new Mac developers make the mistake of exporting their icon from Icon Composer and then using the resulting PNG as their app icon. ## Problem This results in app icons that look overly large in certain situations and on older macOS versions like Sequoia. For example: ![Icon Composer](/assets/images/help/why-your-mac-app-icon-looks-too-big/problem.webp) ## Solution To fix this, we can just drag and drop our generated `AppIcon.icon` directly into Xcode ![Xcode with AppIcon.icon](/assets/images/help/why-your-mac-app-icon-looks-too-big/xcode-icon.webp) We then just need to make sure that in our target under »General« we set the »App Icon« name to the file name of our generated app icon. ![Xcode with AppIcon.icon](/assets/images/help/why-your-mac-app-icon-looks-too-big/xcode-project-setting.webp) ## Icon Composer Inside Icon Composer, you can right-click on the file name to see where your `.icon` file is stored. ![Icon Composer](/assets/images/help/why-your-mac-app-icon-looks-too-big/icon-composer.webp) Having the `.icon` file in Xcode has the advantage that you can open the Icon Composer right from Xcode and can commit it to your git repository. # Notarize and Release a Mac App with GitHub Actions Source: https://amore.computer/help/github-actions/ Amore's [release-action](https://github.com/AmoreComputer/release-action) runs the full release on a GitHub-hosted Mac runner: it archives, code signs, notarizes, signs the update for Sparkle, updates your `appcast.xml` and publishes, no local Mac required. It automates every step of [distributing a macOS app outside the App Store](/blog/distribute-macos-app-outside-app-store/). Push a version tag like `v1.2.0` and a signed, notarized release ships automatically. Tag a prerelease like `v1.2.0-beta.1` and it goes out on your beta channel instead. ## Video {% youtube "9TOZkZyRnow", "Automating macOS releases with GitHub Actions and Amore", "The full CI release: a version tag triggers archive, code signing, notarization and publishing on a GitHub-hosted Mac runner, no local Mac. About seven and a half minutes." %} ## The workflow file Create `.github/workflows/release.yml` in your app's repo. Set the `scheme` to yours and pin `xcode-path`/`runs-on` to the toolchain your app builds with; the rest works as-is: {% raw %} ```yaml name: Amore Release run-name: Release ${{ github.ref_name }} on: push: tags: ['v*'] concurrency: group: amore-release cancel-in-progress: true jobs: release: runs-on: macos-26 steps: - uses: actions/checkout@v4 - name: Read version from tag id: version run: | ref="${GITHUB_REF_NAME#v}" echo "number=${ref%%-*}" >> "$GITHUB_OUTPUT" - uses: AmoreComputer/release-action@v1 with: # Pin Xcode to the version your project builds with, so a runner # default drifting can't silently break your build. scheme: Pomodoro codesign-identity: ${{ secrets.CODESIGN_IDENTITY }} dev-id-cert-p12: ${{ secrets.DEV_ID_CERT_P12 }} dev-id-cert-password: ${{ secrets.DEV_ID_CERT_PASSWORD }} sparkle-private-key: ${{ secrets.SPARKLE_PRIVATE_KEY }} asc-api-key-id: ${{ secrets.ASC_API_KEY_ID }} asc-api-issuer: ${{ secrets.ASC_API_ISSUER }} asc-api-key: ${{ secrets.ASC_API_KEY }} amore-token: ${{ secrets.AMORE_TOKEN }} channel: ${{ contains(github.ref_name, '-') && 'beta' || '' }} marketing-version: ${{ steps.version.outputs.number }} ``` {% endraw %} The release-action builds a DMG installer, which carries a `Built with amore.computer` watermark. It is not removed automatically: with [Amore+](/pricing/) you can turn it off (see [DMG watermark](/help/dmg-creation/#watermark)). ## Add your secrets Add each of the values below in your repo under `Settings` → `Secrets and variables` → `Actions` → `New repository secret`. See [using secrets in GitHub Actions](https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/using-secrets-in-github-actions) for the details. ### App Store Connect API key (notarization) Notarization uses an App Store Connect API key. It gives you three secrets in one go. 1. Open [App Store Connect → Users and Access → Integrations](https://appstoreconnect.apple.com/access/integrations/api) and select the `App Store Connect API` tab. 2. Click the `+` to generate a key. Name it (e.g. `Amore CI`) and give it the `Developer` role. 3. Copy the `Issuer ID` shown above the table → `ASC_API_ISSUER`. 4. Copy the `Key ID` from the new row → `ASC_API_KEY_ID`. 5. Click `Download` to get the `AuthKey_XXXXXXXX.p8` file. You can only download it once. Base64-encode it and paste the result into `ASC_API_KEY`: ```sh base64 -i AuthKey_XXXXXXXX.p8 | pbcopy ``` [Apple's guide to creating API keys](https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api). ### Developer ID certificate (code signing) Signing uses your `Developer ID Application` certificate. If you don't have one yet, create it first (see [Codesigning](/help/codesigning/#codesign-identity)). Export it straight from Xcode, no Keychain Access needed: 1. In Xcode, open `Settings` → `Accounts`, select your Apple ID, then pick your team and click `Manage Certificates`. 2. Control-click your `Developer ID Application` certificate → `Export Certificate`. Save it as a `.p12` and set a password. That password is `DEV_ID_CERT_PASSWORD`. 3. Base64-encode the `.p12` and paste it into `DEV_ID_CERT_P12`: ```sh base64 -i Certificate.p12 | pbcopy ``` Your `CODESIGN_IDENTITY` is the full `Developer ID Application: Your Name (TEAMID)` string. List yours in Terminal and copy it from the output: ```sh security find-identity -v -p codesigning | grep "Developer ID Application" ``` ### Sparkle signing key (updates) `SPARKLE_PRIVATE_KEY` is the base64 Ed25519 private key that signs your updates so existing installs trust them. It is the same key Amore stores in your keychain, do not generate a new one or users won't be able to update. Get it either way and paste the result into the secret: - **From the [CLI](/help/command-line/):** ```sh amore export sparkle-key --bundle-id com.example.YourApp ``` - **From the app:** copy the base64 private key from [Key Management](/help/key-management/). ### Amore token (publishing) `AMORE_TOKEN` is a scoped API key that lets CI publish releases to your Amore-hosted app. Create it in the app: open `Amore` → `Settings…` (`⌘,`) and scroll to `API Keys`. Click `New Key`, name it (e.g. `GitHub Actions`), pick the `Release` scope (enough for CI), and click `Create`. Copy the secret token and paste it into `AMORE_TOKEN`. See [API Keys](/help/api-keys/) for scopes and how to revoke a key. ### Provisioning profile If your app uses a restricted entitlement like Associated Domains, iCloud or Push Notifications, the export additionally needs a Developer ID provisioning profile. Create it once, add it base64-encoded as `AMORE_PROVISIONING_PROFILE`, and pass it in the step: {% raw %} ```yaml provisioning-profile: ${{ secrets.AMORE_PROVISIONING_PROFILE }} ``` {% endraw %} See [Provisioning Profiles](/help/provisioning-profiles/) for more information. ## Running a release Tag a commit and push the tag: ```sh git tag v1.2.0 git push origin v1.2.0 ``` - **`v1.2.0`** → stable release, published as version 1.2.0. - **`v1.2.0-beta.1`** → beta release on your beta channel. The version comes from the tag and the build number is assigned automatically, one past your last release, so there is nothing to bump by hand. ## Self-hosted S3 storage By default your releases are hosted by Amore. To publish to your own S3-compatible bucket (AWS S3, Cloudflare R2, MinIO) instead, add these inputs to the `release-action` step: {% raw %} ```yaml s3-bucket: my-app-releases s3-region: us-east-1 s3-public-url: https://cdn.example.com aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} ``` {% endraw %} Add `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` as repository secrets, and keep `amore-token`; it stays required in S3 mode. For Cloudflare R2 or MinIO, set `s3-region: auto` and add `s3-endpoint: https://.r2.cloudflarestorage.com`. See [Self-Managed S3-Bucket](/help/s3-bucket/) for the full bucket setup. ## Use notarytool with an API key in your own workflow If you keep your own release script, you can still notarize in GitHub Actions with the same three [API key secrets](#app-store-connect-api-key-notarization). `notarytool` reads the key from a file, so decode the secret first: {% raw %} ```yaml - name: Notarize and staple env: ASC_API_KEY: ${{ secrets.ASC_API_KEY }} ASC_API_KEY_ID: ${{ secrets.ASC_API_KEY_ID }} ASC_API_ISSUER: ${{ secrets.ASC_API_ISSUER }} run: | echo "$ASC_API_KEY" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8" xcrun notarytool submit MyApp.dmg \ --key "$RUNNER_TEMP/AuthKey.p8" \ --key-id "$ASC_API_KEY_ID" \ --issuer "$ASC_API_ISSUER" \ --wait xcrun stapler staple MyApp.dmg ``` {% endraw %} - **Sign first.** Submit a DMG, ZIP or PKG that you already signed with your Developer ID certificate. - **`--issuer` is for Team keys.** Leave it out with an Individual key, or the notary service answers `401 Unauthorized`. - **`--wait` blocks until Apple answers.** `stapler` fails when Apple did not accept the build, and that stops the job. The reason is in the log: see [Notarization Errors](/help/notarization-errors/). - **Do not use `altool`.** Apple [ended notarization with `altool`](https://developer.apple.com/documentation/technotes/tn3147-migrating-to-the-latest-notarization-tool) on November 1, 2023. Workflows that call `altool --notarize-app` no longer work. - **Why an API key and not an app-specific password:** a Team key belongs to the team, not to one person's Apple ID, and you can revoke it in App Store Connect at any time. The release-action above does this step for you, together with signing, the DMG, and the Sparkle appcast. # How to Test Your App on Different macOS Versions Source: https://amore.computer/help/how-to-test-your-app-on-different-macos-versions/ When you distribute your app, your users may be running different macOS versions. Testing on multiple versions helps you catch compatibility issues before your users do. The easiest way to do this without needing extra hardware is by running a virtual machine with [VirtualBuddy](https://github.com/insidegui/VirtualBuddy). ## What is VirtualBuddy? VirtualBuddy is a free, open-source macOS app that lets you create and manage macOS virtual machines using Apple's `Virtualization` framework. It supports running macOS Monterey (12) and later, so you can test your app across a wide range of versions. ## Requirements - A Mac with Apple Silicon - macOS Ventura (13) or later on the host machine - Enough free disk space for the macOS installer and VM (~30-60 GB per VM) ## Setting Up a Virtual Machine 1. Download [VirtualBuddy](https://github.com/insidegui/VirtualBuddy) from GitHub 2. Open VirtualBuddy and create a new virtual machine 3. Select the macOS version you want to install. VirtualBuddy will download the restore image for you 4. Follow the on-screen setup to complete the macOS installation You can create multiple VMs for different macOS versions to cover your supported range. ## Quick Testing With a Built App If you don't need the full development environment, you can copy a built app into the VM instead. VirtualBuddy supports shared folders between your Mac and the virtual machine, so you can quickly move your `.app` bundle or `.dmg` into the VM for testing. Alternatively, you can drag and drop files directly into the VM window. ## Developing Directly Inside the VM You can install Xcode inside the VM and build, run, and debug your app there, just like on your host Mac. This works on macOS 15 (Sequoia) or later VMs and supports apps with iCloud and other Apple service entitlements. 1. Install Xcode inside the VM from the App Store or [developer.apple.com](https://developer.apple.com/xcode/) 2. Open System Settings and sign in with your Apple Account 3. Open Xcode and add your Apple Account under Settings → Accounts 4. Access your project through a shared folder and build and run as usual This gives you the full development experience — breakpoints, console output, and profiling — on whichever macOS version the VM is running. ## Tips - Test both your oldest supported macOS version and the latest one - Pay attention to deprecated APIs that may behave differently across versions - Use shared folders to access your project source code inside the VM — no need to clone your repository separately # Developer ID Provisioning Profiles Source: https://amore.computer/help/provisioning-profiles/ Most [Developer ID apps](/blog/distribute-macos-app-outside-app-store/) ship without a provisioning profile. Some entitlements, Apple calls them restricted, only work when a profile that authorizes them is embedded in the app. Associated Domains (`applinks:`, `webcredentials:`) is the common one; iCloud, Push Notifications and App Groups are others. If your app carries one of these entitlements, a release fails with: ``` The app's entitlements require a provisioning profile. ``` On CI this usually surfaces at the export step: the archive builds fine, and xcodebuild only demands the profile when it re-signs the app for Developer ID distribution. ## Create a Developer ID provisioning profile You only need do this once. 1. Open [Certificates, Identifiers & Profiles → Identifiers](https://developer.apple.com/account/resources/identifiers/list) and make sure your app's identifier has the capability enabled 2. Switch to [Profiles](https://developer.apple.com/account/resources/profiles/list) and click the add button (+). 3. Under `Distribution`, select `Developer ID` and continue. 4. Select your App ID, then select your `Developer ID Application` certificate. It must be the certificate you sign releases with (the one behind `DEV_ID_CERT_P12` in [GitHub Actions](/help/github-actions/), or your [codesign identity](/help/codesigning/#codesign-identity)). 5. Name the profile (e.g. `YourApp Developer ID`) and download the `.provisionprofile` file. ## Use it in GitHub Actions Base64-encode the profile and add it as a repository secret named `AMORE_PROVISIONING_PROFILE`: ```sh base64 -i YourApp.provisionprofile | pbcopy ``` Then pass it to the [release-action](/help/github-actions/) step: {% raw %} ```yaml provisioning-profile: ${{ secrets.AMORE_PROVISIONING_PROFILE }} ``` {% endraw %} ## Use it with the CLI Pass the file directly: ```sh amore release --provisioning-profile YourApp.provisionprofile ``` Or provide it base64-encoded via the `AMORE_PROVISIONING_PROFILE` environment variable, which is what the GitHub Action does under the hood. Either way, Amore derives the signing identity from the profile's own certificates, signs the archive and export manually, and embeds the profile in the app. The whole release runs offline, with no contact to Apple's provisioning service. ## Why Amore cannot fetch the profile automatically On your Mac, where Xcode has an Apple ID session, Amore lets `xcodebuild` fetch missing profiles automatically and no setup is needed. On CI you would expect App Store Connect API credentials to do the same through `xcodebuild`'s cloud signing, and Amore does try exactly that. But Apple's provisioning service currently rejects API-key authentication for Developer ID certificates, regardless of the key's role: even Admin keys fail with `Cloud signing permission error`. Apple has confirmed this limitation ([FB16835802](https://developer.apple.com/forums/thread/776036)). Until it is fixed, a pre-created profile is the only fully headless path, and your ASC key can stay at the `Developer` role it needs for notarization. ## When the profile stops working A Developer ID profile stays [valid for 18 years](https://developer.apple.com/support/developer-id/), even after its certificate expires. But it lists the certificates and capabilities it accepts. Regenerate the profile with the same steps and update the secret when you get a new certificate, when a certificate in it is revoked, or when you add or remove a capability on the App ID. With a new certificate, also update `DEV_ID_CERT_P12` and `DEV_ID_CERT_PASSWORD` (see [GitHub Actions](/help/github-actions/)). # Sparkle Sandboxing Guide Source: https://amore.computer/help/sparkle-sandboxing/ This guide shows how to integrate Sparkle into a sandboxed app using the `App Sandbox (com.apple.security.app-sandbox)` entitlement. If this guide does not meet your requirements, you can learn more in [Sparkle's sandboxing guide](https://sparkle-project.org/documentation/sandboxing/). ## XPC Services Sparkle uses 2 XPC services inside the framework to update your application: - Installer.xpc - Downloader.xpc ### Installer The installer service is required to update your application when sandboxed. You enable this service by setting the `SUEnableInstallerLauncherService` to `YES` in your Info.plist file. ```xml SUEnableInstallerLauncherService ``` To allow your app to communicate with Sparkle's installer services, you need to add the following temporary exceptions to your `.entitlements` file: ```xml com.apple.security.temporary-exception.mach-lookup.global-name $(PRODUCT_BUNDLE_IDENTIFIER)-spks $(PRODUCT_BUNDLE_IDENTIFIER)-spki ``` ### Downloader For Sparkle to be able to fetch and download new updates, your application needs the Outgoing Client Connections (`com.apple.security.network.client`) entitlement. You can either enable the checkmark in Xcodes under Signing & Capabilities or add the following to your entitlements file: ```xml com.apple.security.network.client ``` # FAQ Source: https://amore.computer/help/faq/ ## Is Amore free? Amore will always be free for [*FOSS*](/foss/) projects without limitations. If you are working on one and are interested to use Amore for free please [reach out to me](mailto:xoxo@amore.computer?subject=Amore). For everyone else, the free tier has these limits: - 2 releases per month on Amore's cloud storage. - [DMGs](/help/dmg-creation/) contain a 'Built with Amore' watermark. - Only the latest release is available for download. - No [custom domain](/help/custom-domain/) and no analytics. [Amore+](/pricing/) removes every limit for **$99 per year** or $9.99 per month. ## Can I use my own S3 bucket? Amore is primarily designed around DX and convenience, which is why the initial version used Amore's server to host Sparkle's `appcast.xml` and releases. This gives us more freedom to experiment with advanced Sparkle features like download analytics and [anonymous system profiling](https://sparkle-project.org/documentation/system-profiling/) and more. Amore's intent is not to lock you in. It currently provides the option to use a custom domain to serve the `appcast.xml` so you stay in full control. If you want even more control, or need to use an existing S3 bucket you can do that too. [Learn More](/help/s3-bucket) ## Can I install Amore with Homebrew? Yes. Amore is available as an official [Homebrew](https://brew.sh) cask. ```sh brew install amore ``` The cask also links the `amore` [command-line tool](/help/command-line/), so it's ready to use in your terminal right after install. Amore still updates itself via Sparkle, so `brew upgrade` mostly just bumps the cask metadata while Sparkle handles the actual app update on next launch. ## How does licensing work? Amore's licensing system lets you sell license keys for your macOS app through Stripe. You create a product, connect your Stripe account, integrate the AmoreLicensing SDK, and share a checkout link with your customers. When a customer completes a purchase, they receive their license key via email and can activate it in your app. [Learn More](/help/licensing/) ## Can I sell in-app without the App Store? Yes. StoreKit is App Store only, but you don't need it. `AmoreCheckout` presents Stripe checkout in a sheet inside your app, fetches the license key when payment clears, and activates it for you. Your customer clicks buy and the app unlocks, with no browser detour, no key to paste, no App Store cut, and no review. [Learn More](/help/in-app-purchases/) ## Can I use Paddle or another merchant of record? You don't need one. Stripe can be the merchant of record itself through [Stripe Managed Payments](https://docs.stripe.com/payments/managed-payments): it calculates, collects, and remits VAT and sales tax worldwide, and takes on fraud and chargebacks. It's a setting in your own Stripe account, so there is no reseller between you and your customers, and Amore licensing works with it unchanged. Selling directly and handling tax yourself stays an option. [Learn More](/licensing/) # “App Is Damaged and Can’t Be Opened”: The Fix for Mac Developers Source: https://amore.computer/blog/app-is-damaged-and-cant-be-opened/ A user downloads your Mac app, opens it, and macOS says: > “MyApp.app” is damaged and can’t be opened. You should move it to the Trash. Many articles about this alert tell the user to run `xattr -cr` in Terminal. At best, that hides the problem on one Mac. If your users see this alert, your release is broken, and only you can fix it. This post shows how to reproduce the alert, the three causes on the developer side, and the fix for each. It covers one step of [distributing a macOS app outside the App Store](/blog/distribute-macos-app-outside-app-store/). ## What the alert means The download is usually not corrupt. Gatekeeper checks every app that arrives with the quarantine flag, and this alert means the code signature of the app is broken. Apple's [support page](https://support.apple.com/en-us/102445) says it appears when "macOS detects that software has been modified or damaged". Two things follow from that: - **The app can still work on your Mac.** Your build never got the quarantine flag. In our tests, Gatekeeper did not block a broken app without the flag, unless the app was notarized. Apple [notes](https://developer.apple.com/forums/thread/706442) that tools like `curl` and `scp` do not set the flag either. - **It is not a notarization problem.** A correctly signed app that is not notarized gets a different alert: “MyApp.app” Not Opened, with the text [Apple could not verify “MyApp.app” is free of malware](/help/notarization-errors/#apple-could-not-verify-myapp-is-free-of-malware). An app with no signature at all gets that alert too. Apple also says that the exact wording of Gatekeeper alerts changes between macOS releases. The alert texts in this post are from macOS 27.2 on Apple silicon. The checks below do not depend on the wording. ## Reproduce it first Test the file that your users get, the way they get it: 1. Upload the release to its real download URL. 2. Download it with Safari, so that it gets the quarantine flag. Check the flag with `xattr -p com.apple.quarantine MyApp.dmg`. 3. Open it on a Mac that has never seen your app. Gatekeeper caches its results, so Apple recommends a fresh machine for each test. A virtual machine with a snapshot is the easy way. Then run these commands on the downloaded copy, not on your build output: ```sh codesign -v -vvv --strict --deep MyApp.app syspolicy_check distribution MyApp.app ``` `syspolicy_check` needs macOS 14 or later. On older systems, use `spctl -a -t exec -vvv MyApp.app`. The `codesign` output tells you which of the three causes you have. ## Cause 1: the bundle was never signed ```sh codesign -dvv MyApp.app ``` If the output has `flags=0x20002(adhoc,linker-signed)` and `Sealed Resources=none`, this is your cause. Apple silicon does not run native code that has no signature, so the linker gives every arm64 executable an ad-hoc signature. That signature covers the executable, not the bundle. It is enough to run the app on the Mac that built it. It is not enough for Gatekeeper: after a download, the app is "damaged". `codesign -v` says `code has no resources but signature indicates they must be present`. Projects that ship builds without a Developer ID signature report this alert, for example [Ardour](https://discourse.ardour.org/t/ardour-7-on-m1-pro-ardour7-app-is-damaged-and-can-t-be-opened-you-should-move-it-to-the-trash/107700) and [IPTVnator](https://4gray.github.io/iptvnator/blog/macos-arm-app-damaged-fix). An ad-hoc signature for the whole bundle is a different case. `codesign -s -` makes one, and Apple's engineers say that ["Sign to Run Locally"](https://developer.apple.com/forums/thread/775605) is Xcode's name for it. In our test, that app got the “MyApp.app” Not Opened alert, not this one. The fix is the same. **Fix:** sign with a `Developer ID Application` certificate, with the hardened runtime and a secure timestamp, then notarize. This needs a paid Apple Developer Program membership. See [Code Signing and Notarization](/help/codesigning/). ## Cause 2: the bundle changed after you signed it `codesign` reports: ``` MyApp.app: a sealed resource is missing or invalid file added: MyApp.app/Contents/Resources/config.json ``` The signature seals the files in the bundle. A `file added`, `file modified` or `file missing` line names a file that changed after signing. A changed `Info.plist` gives `invalid Info.plist (plist or signature have been modified)`. For a notarized app, `syspolicy_check` names the file too: `File added after outer app bundle was codesigned.` For an app that is not notarized, it only reports the missing ticket. Typical sources are a build script that runs after the signing step, or a release script that edits `Info.plist` or copies a file into the exported app. **Fix:** make signing the last step that touches the bundle. If you must change the app after export, sign it again from the inside out, and notarize the new build. ## Cause 3: non-ASCII file names in the bundle `codesign` again reports `a sealed resource is missing or invalid`, with the same name in two lines: ``` MyApp.app: a sealed resource is missing or invalid file added: MyApp.app/Contents/Resources/café.png file missing: MyApp.app/Contents/Resources/café.png ``` A file name like `café.png` has two valid Unicode forms, precomposed and decomposed. If the name changes from one form to the other on the way to the user, the file is the same, but its name no longer matches the signature. In our tests, an HFS+ disk image (`hdiutil create -fs HFS+`) and `tar` changed a precomposed name to the decomposed form. `ditto`, `cp -R`, and `zip -ry` with `unzip` kept the name. Apple's engineers report the same break for [a copy in Finder](https://developer.apple.com/forums/thread/725204) and for [the Compress command in Finder](https://developer.apple.com/forums/thread/706379). They [call this Apple's bug](https://developer.apple.com/forums/thread/725204), and they also say: "the only good solution is to stick to ASCII file names within your bundle." Find the files: ```sh find MyApp.app | LC_ALL=C grep '[^ -~]' ``` **Fix:** rename them to ASCII, including localized resources and files inside embedded frameworks, then sign and notarize again. ## Not this alert, but close: `zip -r` breaks the bundle A ZIP file made with plain `zip -r` also breaks the signature. `codesign` reports: ``` MyApp.app: bundle format is ambiguous (could be app or framework) In subcomponent: MyApp.app/Contents/Frameworks/Sparkle.framework ``` Frameworks contain symbolic links such as `Versions/Current`. Plain `zip -r` does not store symbolic links: it stores a copy of the target in their place. The unzipped framework is no longer a valid bundle, and the signature fails. We tested this with our own notarized app that embeds one framework, and with a notarized app that embeds Sparkle. We unpacked each archive with `ditto -x -k`. The `zip -r` archives failed `codesign` verification. The `zip -ry` and `ditto -c -k --keepParent` archives passed. After `zip -r`, our notarized app got the “MyApp.app” Not Opened alert, not the "damaged" alert, with and without a stapled ticket. **Fix:** create the archive with the command from Apple's [packaging guide](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution): ```sh ditto -c -k --keepParent MyApp.app MyApp.zip ``` A signed disk image is the better container. You can sign a DMG and staple the notarization ticket to it. You cannot do either with a ZIP file. ## Do not tell users to run xattr `xattr -cr MyApp.app` removes the quarantine flag. In our tests, that opened a broken app only when the app was not notarized. A notarized app with a broken seal got the same alert again, without the flag. Even when the app opens, the release is still broken. Every new user hits the same alert, most of them will not open Terminal, and those who do learn to switch off a security check for your app. Ship a fixed build. ## Check every release Before you announce a release, download it with Safari on a clean Mac or VM and open it. For a release script, add `codesign -v --strict --deep MyApp.app`. It exits with a non-zero status for each of the three causes. `syspolicy_check distribution MyApp.app` also checks notarization, but it fails for an app from a DMG when the ticket is stapled only to the DMG. ## Skip the manual steps Amore signs the app with your Developer ID, notarizes it, and packs it into a DMG as part of one command. We tested the DMG step with `amore create-dmg`: the DMG came out signed, notarized and stapled, and an app with cause 1 stopped the command with a notarization error. For cause 3, keep the file names in your bundle ASCII. Set it up in [Code Signing and Notarization](/help/codesigning/), then release with the [CLI](/help/command-line/) or [GitHub Actions](/help/github-actions/). For other signing and notary failures, see [Notarization Errors](/help/notarization-errors/). ## Sources - [Apple Support: Safely open apps on your Mac](https://support.apple.com/en-us/102445) - [Apple Developer Forums: resolving Gatekeeper problems](https://developer.apple.com/forums/thread/706379) - [Apple Developer Forums: resolving trusted execution problems](https://developer.apple.com/forums/thread/706442) - [Apple Developer Forums: testing a notarized product](https://developer.apple.com/forums/thread/130560) - [Apple Developer Forums: notarization invalidated by a Finder copy](https://developer.apple.com/forums/thread/725204) - [Apple Developer Forums: Sign to Run Locally is ad-hoc code signing](https://developer.apple.com/forums/thread/775605) - [Packaging Mac software for distribution](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution) # Developer ID Certificates Expire February 1, 2027: What to Do Source: https://amore.computer/blog/developer-id-certificate-expires-2027/ On October 1, 2026, Apple [announced](https://developer.apple.com/news/?id=w4atic4c) that the original Developer ID Certification Authority expires on **February 1, 2027**. Every Developer ID certificate issued by that authority stops working on the same day. If you ship a Mac app outside the App Store, you may have signed it with one of these certificates. Here is what breaks, how to check, and how to replace it. ## What stops working - **Installer packages:** `.pkg` files signed with an affected certificate will no longer install, starting February 1, 2027. - **New releases:** you cannot sign new builds or updates with the old certificate after that date. - **Apps you already shipped:** these keep working. Apple says software that is already signed and notarized with a secure timestamp needs no action. Re-sign every `.pkg` with a new certificate before February 1. Your next release also needs the new certificate. ## Check if your certificate is affected 1. Open Keychain Access and find your `Developer ID Application` certificate. Skip the `Developer ID Certification Authority` entry: that is the authority itself, and its issuer always shows `Apple Certification Authority`. 2. Expand **Issuer Name** and look at **Organizational Unit**. 3. If it says `Apple Certification Authority`, the certificate comes from the expiring authority. Replace it. 4. If it says `G2`, you are fine. From the terminal, print the subject and issuer of every Developer ID Application certificate in your keychain: ```sh security find-certificate -a -c "Developer ID Application" -p \ | openssl crl2pkcs7 -nocrl -certfile /dev/stdin \ | openssl pkcs7 -print_certs -noout ``` An issuer with `OU=G2` is the new authority. An issuer with `OU=Apple Certification Authority` is the expiring one. Do the same check for your `Developer ID Installer` certificate if you ship `.pkg` files. In the command, replace `-c "Developer ID Application"` with `-c "Developer ID Installer"`. ## Replace it You need the Account Holder role on the team. 1. Sign in to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list). 2. Click the add button (+). 3. Under Software, select **Developer ID Application** (or **Developer ID Installer**) and click Continue. 4. When asked for the Developer ID Certificate Intermediary, choose **G2 Sub-CA (Xcode 11.4.1 or later)**. Apple warns that any other option can issue another certificate from the expiring authority. 5. Upload a certificate signing request, download the certificate, and double-click it to install it. Repeat these steps for each certificate type you use. A Developer ID Application certificate does not cover installer packages, and the reverse is also true. A team can hold up to five Developer ID Application and five Developer ID Installer certificates at a time, so you can create the new one now and test it before the old one expires. If your team already holds five, revoke one you no longer use. Never revoke a certificate that signed a build you shipped: Apple says an app signed with a [revoked Developer ID certificate](https://developer.apple.com/help/account/certificates/create-developer-id-certificates/) can no longer be installed, and stops launching if it is already installed. Apple says certificates from the G2 authority are [valid for one year and must be renewed annually](https://developer.apple.com/help/account/certificates/replace-developer-id-certificates/). Put the expiry date of your new certificate in your calendar. ## Update everything that signs your app The new certificate only helps if every build uses it: - **Your Mac:** select the new identity wherever you sign. In Amore, pick it in the [Code Signing settings](/help/codesigning/#codesign-identity). The old and new certificates have the same name, so `codesign` can stop with an "ambiguous" error while both are in your keychain. After the switch, delete the old certificate from your keychain, or sign with the SHA-1 hash that `security find-identity -v -p codesigning` prints. - **CI:** export the new certificate as a `.p12` with a new password, and replace both secrets. For the Amore GitHub Action these are `DEV_ID_CERT_P12` and `DEV_ID_CERT_PASSWORD`. See [GitHub Actions](/help/github-actions/). - **Provisioning profiles:** a Developer ID provisioning profile lists the certificates it accepts. If your app uses one, regenerate it with the new certificate. See [Provisioning Profiles](/help/provisioning-profiles/). - **Sparkle:** do not generate a new EdDSA key. Your update signing key has nothing to do with the Developer ID certificate. Then ship one release with the new certificate, well before February 1, and confirm that notarization passes and that an existing install updates to it. # In-App Purchases Without the Mac App Store Source: https://amore.computer/blog/in-app-purchases-without-the-mac-app-store/ If you ship your Mac app outside the App Store, you give up StoreKit. There is no `Product.purchase()` waiting for you, no App Store sheet, no receipt validation. Apple's in-app purchase framework only works for apps distributed through Apple's store, and there is no independent-developer edition of it. That does not mean you have to send people to a browser to buy your app. It just means you have to assemble the flow yourself, and most people assemble a worse one than they need to. ## Why sell outside the store at all The Mac App Store takes 30% of every sale, or 15% if you qualify for the Small Business Program. That's the number everyone quotes, but the developers I talk to rarely leave over the money alone. They leave because App Review gates every bug fix, because sandboxing rules out what their app actually does, or because they want to know who their customers are. If you're weighing that trade-off, I wrote a longer piece on [distributing a Mac app outside the App Store](/blog/distribute-macos-app-outside-app-store/). This post is about the part that comes after you've decided: getting paid. ## What you actually have to build Selling a license yourself means owning four things that StoreKit handled for you: 1. **Payments.** Taking a card, handling subscriptions, refunds, and failed renewals. 2. **Tax.** VAT and sales tax in every jurisdiction you sell into. 3. **License keys.** Issuing them, delivering them, and revoking them. 4. **Activation and validation.** Deciding, on a Mac that might be offline, whether this copy is paid for. Miss any one and you feel it. Skip tax and you eventually get a letter. Skip validation and your app shows up on a warez site with a keygen next to it. ## The options, honestly **A merchant of record** like Paddle or Lemon Squeezy takes payments and tax off your plate entirely, in exchange for a percentage that typically lands in the mid single digits plus a fixed fee per transaction. Cheaper than the App Store, more expensive than Stripe, and you get a reseller sitting between you and your customer. **Gumroad and similar** are the fastest thing to set up and the most expensive to keep. Fine for a first hundred sales, painful at a thousand. **Stripe directly** is the cheapest per transaction by a wide margin, and it leaves you holding items 3 and 4 on that list, plus tax unless you switch on Stripe's managed payments, which makes Stripe itself the merchant of record. **Rolling everything yourself** is a weekend to prototype and a permanent part-time job to run. Key servers get DDoSed, signing keys leak, grace periods turn out to matter the first time your server has an outage while a customer is on a plane. There's no universally right answer here. But notice that all four leave the same hole. ## The hole: the browser handoff Whatever you pick, the default integration looks like this. Your app shows a paywall with a Buy button. The button opens a browser. Your customer pays. A key arrives by email, eventually. They find the email, copy the key, switch back to your app, and paste it into a text field. Count the exits. Any tab they already have open, any notification, any point where the email is slow or lands in spam. Every one of those steps loses a fraction of the people who had already decided to pay you. The ones who make it through are also the ones who write to you when the email doesn't arrive, so the flow costs you support time on top of conversions. The App Store version of this is one sheet and a Touch ID prompt. That gap is the real cost of leaving the store, and it's much bigger than 30%. ## What a good flow looks like instead You can close that gap without StoreKit. The pieces: - **Checkout inside the app.** An embedded web view presenting the payment page, so the purchase happens in a sheet rather than in Safari. Sandboxed apps need the outgoing-connections entitlement for this. - **Automatic activation.** When payment clears, the app fetches the key and activates it itself. The customer never sees a license key unless they go looking for one. - **Reactive unlocking.** Your gate is an observable license status, so the UI unlocks the moment activation succeeds instead of asking anyone to restart. - **Interrupted purchase recovery.** This is the one people forget. If the app quits after the card is charged but before the license is stored, you need to resolve that session at next launch, and you need starting a new purchase to join the existing session rather than charging again. Get this wrong and you will double-charge someone. - **Offline validation.** A signed token checked locally, with a grace period, so an outage on your side doesn't lock out paying customers. - **A browser escape hatch.** Apple Pay requires the external browser, and some customers just prefer it. The app should activate the license either way. None of that is exotic. It's just several days of work that has nothing to do with the app you actually wanted to build, and the failure modes only show up in production with real money attached. ## Doing it with Amore This is the flow [Amore](/) ships. Licensing gives you keys, activation, offline validation, and a customer portal on top of your own Stripe account. [AmoreCheckout](/help/in-app-purchases/) is the part that keeps the purchase inside your app. The integration is one modifier: ```swift import AmoreCheckout let checkout = AmoreCheckout(licensing: licensing) ``` ```swift @State private var buying: Product? Button("Buy Pro") { buying = product } .amoreCheckout(item: $buying, checkout: checkout) ``` When payment completes, the key is fetched and activated on that Mac, and `licensing.status` flips to `.valid`, so anything you gate on it unlocks by itself. Recovery for an interrupted purchase is one call at launch: ```swift await checkout.recoverPendingPurchase() ``` Starting checkout guards the same pending session, so a customer who buys again before recovery runs joins the existing purchase instead of paying twice. The sheet is a thin layer over an observable object, so if you'd rather build your own paywall, your own success screen, or run checkout in the external browser, you drive the same object and get the same activation. The [in-app purchases guide](/help/in-app-purchases/) covers each of those, and [Pomodoro](https://github.com/AmoreComputer/Pomodoro) is an open-source Mac app with the whole thing wired up, updates included. On cost: payments run through your own Stripe account at Stripe's rates, and Amore's licensing adds 1.5% of monthly tracked revenue once you're over $1,000 in a month, nothing below that. If you want to be out of the tax business, Stripe Managed Payments makes Stripe the merchant of record and the integration doesn't change. The catch worth naming: this is Stripe-only, so if you're committed to a different reseller, it isn't for you. ## The short version StoreKit is off the table outside the App Store, but the thing StoreKit is actually good at, buying without leaving the app, is reproducible. Embedded checkout, automatic activation, and honest handling of interrupted purchases get you most of the way to a first-party feel while keeping your margin, your release schedule, and your customer list. [Get started with in-app purchases](/help/in-app-purchases/) or read the [licensing guide](/help/licensing-guide/) first if you haven't set up products yet. # With Amore №6 Source: https://amore.computer/blog/6/ Hi friends, This is the sixth issue of »With Amore«, which I use to keep you in the loop about significant project milestones and updates. In June and July, the focus was on improving Amore for teams and CI pipelines. Additionally, Amore's Swift SDK, AmoreKit, got numerous updates, like in-app purchases and cross-platform support. ### Amore for Teams Amore is used by multiple teams, like [Monologue](https://monologue.to) by Every. In the past, the Monologue team would ship new code, and the product owner had to go through the Amore CLI or Amore Mac app to manually release a new version. That person quickly became the bottleneck of the team. As commonly known in software engineering, short feedback loops are essential for fast shipping teams. To fix this, the Amore CLI got reworked to be fully usable in CI/CD environments. It now has the option for multiple release channels and supports Amore API keys so the whole Amore pipeline can be automated in the cloud. In addition, there's now a [GitHub release action](https://amore.computer/help/github-actions/) to make it easy to automate the release of any macOS project that's hosted on GitHub. It's highly customizable and includes instructions to automatically publish new releases on an alpha or beta channel after pushing to `main` and to the stable channel after adding a release tag. It's already been used by multiple teams and individuals to release continuously. ### AmoreCheckout In May, Amore added a fully functional licensing system (`AmoreLicensing`) that's already used by a dozen apps. One drawback compared to the Mac App Store was that it used the traditional Mac licensing approach. The app would link to a pricing or checkout page, causing the user to leave the app to open a browser. The customer would then input their credit card information and get a licensing key via email, which they manually have to copy and paste back into the app to activate their license, causing support requests and lowering the conversion rate. [AmoreCheckout](https://docs.amore.computer/documentation/amorecheckout) is the latest addition to [AmoreKit](https://github.com/AmoreComputer/AmoreKit) which provides in-app purchases to every Mac app using `AmoreLicensing`. It's an optional addition that enables a smooth checkout experience that happens completely inside your app. It's highly customizable and automatically activates the license after a successful purchase. You can see the whole experience in Amore's open-source demo project, [Pomodoro](https://github.com/AmoreComputer/Pomodoro). ### AmoreKit In addition to `AmoreCheckout`, various parts of the Swift SDK, `AmoreKit` received little updates. It now exposes more information about the user's license, like their email address, and makes all licensing data available synchronously at launch. It also got lighter by dropping the heavy `JWTKit` dependency. `AmoreLicensing` is now platform-independent and can be used on any platform Swift compiles for, not just macOS. ### Onboarding & Docs Like announced in the last post, the Mac app's onboarding experience improved to make it easier for new users to get started. Additionally, the [agent skill](https://github.com/AmoreComputer/amore-skill) got updated, and I added better agent instructions on [amore.computer](https://amore.computer) to make adding an app to Amore even easier. ### Built with Amore Like in previous months, I want to thank early supporters and adopters of this project that helped shape Amore into the tool it is today. ***[Billy](https://usebilly.app)*** Billy is a delightful invoice manager crafted with love for all the freelancers and small businesses that want their bureaucracy to be a little more organized and beautiful. It's one of the best new Mac apps I have seen this year. ***[Notch](https://notch.two.supply)*** Notch is a delightful little app that makes it easy to add inspiring artifacts from your Mac to your [Are.na](https://are.na) spaces. ### What's Next? For the rest of the summer, I plan to improve the help docs and create more videos about various advanced Amore features to make it easier for developers to get started and get the most out of Amore. Additionally, I am still keen to look into an app directory and app landing pages. This will require some thinking and working closely with other developers, which I am looking forward to. I would like to personally onboard more developers to AmoreLicensing. If you consider Amore as your licensing system, reply to this email to get in touch. I would love to talk to you. xoxo\ Lucas # With Amore №5 Source: https://amore.computer/blog/5/ Hi friends, This is the fifth issue of »With Amore«, which I use to keep you in the loop about significant project milestones and updates. In May the focus was on improving the licensing & payments system for the first couple of developers that implemented it into their apps. ### Amore Licensing One of the most significant changes this month is that Amore's licensing system does not require an Amore+ subscription anymore, making it absolutely free to get started. **Only pay in months you earn over $1,000. Thereafter, it's 1.5% of your total monthly tracked revenue**. [Learn More](https://amore.computer/licensing/) ### App & CLI The biggest change in the app and CLI last month was the license manager, making it possible to view and manage issued licenses for your apps. This makes it possible to see who your customers are and to help them with possible support requests. You can see all license data and can revoke licenses and device activations. Additionally, you can now issue licenses right from inside the app. Perfect for testing and giveaways. DMGs are now created faster and more reliably and don't open a new Finder window during the creation process. ### AmoreKit It was a good month for [AmoreKit](https://github.com/AmoreComputer/AmoreKit/releases). It got implemented in a couple of apps that, since then, shipped. Not only that, but it received its first open-source contribution, making it more powerful and flexible. Thanks to a new protocol, it's now possible to bring your own token storage. Licenses now expose the product that they are valid for. Additionally, licenses include the current state of a connected subscription, making it possible to detect if a license is still valid but cancelled, for example. Finally, AmoreKit gained a new [`AmoreStore`](https://docs.amore.computer/documentation/amorestore) library that can be used to fetch products, their prices, and checkout URLs. This makes it possible to build paywalls that use the SDK as a single source of truth. ### Homebrew Amore is now on Homebrew and can be installed via `brew install amore`. ### What's Next? In June I plan to improve the help docs and onboarding experience to make it easier for new developers to get started. Additionally, I am keen to look into an app directory and app landing pages. This will require some thinking and working closely with other developers, which I am looking forward to. I would like to personally onboard more developers to AmoreLicensing. If you consider Amore as your licensing system, reply to this email to get in touch. I would love to talk to you. xoxo\ Lucas # With Amore №4 Source: https://amore.computer/blog/4/ Hi friends, This is the fourth issue of »With Amore«, which I use to keep you in the loop about significant project milestones and product updates. In March and April the focus was on the licensing & payments system, which got polished and is now publicly available. This is the biggest update since Amore was launched and marks the end of phase 2 outlined in the original essay about [My Vision for macOS App Distribution](https://lucas.love/blog/vision-macos-app-distribution). ### Licensing & Payments It was important to make the Amore licensing system approachable for indie hackers and hobbyists, which is why you can start to use the new Amore licensing system for free. **Only pay in months you earn over $1,000. Thereafter, it's 1.5% of your total monthly tracked revenue (MTR)**. [**Learn More**](https://amore.computer/licensing/) Amore's licensing system includes unlimited access to: - Product checkout page - License key generation - Purchase/subscription success page with license key - Email delivery of license key - AmoreLicensing Swift SDK - License activation and validation server To get started, all you need to do is integrate the [AmoreLicensing SDK](https://amore.computer/help/licensing-guide/#integrate-amoramore.computer) into your app and connect your Stripe account to Amore. If you prefer to learn from a working example, I put together [Pomodoro](https://github.com/AmoreComputer/Pomodoro), a small SwiftUI app that shows the full Sparkle + licensing setup end-to-end. ### Agent Skill Everything you can do within the Amore app, you can do via the command line. This includes the new licensing system and product configuration. Because this is the only way some developers interact with Amore, we created a Claude Code plugin and agent skill you can use to interact with Amore with guidance from Amore's documentation. [**Learn More**](https://github.com/AmoreComputer/amore-skill) ### Built with Amore Like last month, I want to thank early supporters and adopters of this project that helped shape Amore into the tool it is today. Here are some of the highlights of the ~50 new apps that started relying on Amore since March. [***SlapMac***](https://slapmac.com) This one has gone quite viral since the end of March. You may have already heard about it. Tonino, the developer, describes his app as »Slap your MacBook. It screams back. That's it. That's the app.« It's a fun little app that already got more than 90k downloads. [***Markdown Preview***](https://markdownpreview.app) One of those apps you should install on every new Mac. It adds native support for Markdown previews to CMD + SPACE and includes a beautiful Markdown document reader. It's free and open-source. Go get it. [***Doorry***](https://altumdream.com/doorry/) Your Mac has more open ports than you think, and most apps never close the ones they used. Doorry lives in the menu bar and shows what's actually listening, with a kill button that works. [***Pluk***](https://pluk.sh) An AI-native macOS database client for Postgres, MySQL, SQLite, MongoDB & Convex. AI does the querying, you explore your data. [***Coca***](https://coca.cammalleri.dev) Keep your Mac from falling asleep with one click! Stay green in all your messengers. [***Peek***](https://mattspear.gumroad.com/l/peekapp) This is a smaller project from my friend Matt. Peek lets you monitor your OpenAI API spending right in the menu bar, with a per-project breakdown in the dropdown. No browser tab, no dashboard – just the number you need. ### What's Next? As the work on the licensing system is complete, I will focus on documentation and help articles to make it easier for new developers to get started. Because Amore now hosts over 100 apps, I was wondering if it could be interesting to have an opt-in app store-like directory of apps. You choose some screenshots and a description and have a landing page you can share. It would include a download and optional payment link. Especially useful for small apps you would rather not build a website for. I would like to personally onboard the first developers to AmoreLicensing. If you consider Amore as your licensing system, reply to this email to get in touch. I would love to talk to you. xoxo\ Lucas # With Amore №3 Source: https://amore.computer/blog/3/ Hi friends, This is the third issue of »With Amore«, which I use to keep you in the loop about significant project milestones and product updates. At the beginning of February, I focused on making the `amore` CLI more powerful and easier to use for humans and their agents. I also made significant progress towards a powerful licensing and payments system. ### CLI In short, the `amore` CLI reached feature parity with the Mac app, which means that everything you can do using the Amore app can now be done in the command line. This is especially useful for automation using scripts or coding agents. One highlight is additions to the `amore release` command, which now allows you to release a new version of your app right from the project folder without the need for manually archiving and notarizing. The currently fastest way to publish new releases with Amore. It's now also possible to let an agent complete the initial Sparkle and Amore setup for your app. The `amore setup` command registers new apps, and with `amore config` you or your agent can change the release settings of your app. Tell your agent about the `amore help` command to get them started. [**Learn More**](https://amore.computer/help/command-line/) ### Licensing & Payments Another big focus in February was the Amore licensing and payments system, which will be publicly available in March. If you are interested, please reply to this email. Early developers get lasting, better conditions before the public release. The licensing system consists of a secure Swift SDK called [AmoreLicensing](https://docs.amore.computer), a licensing server, a self-serve customer portal for your users, and a powerful API for 3rd-party payment providers. Amore currently supports issuing new licenses via CLI, API, and Stripe webhooks. Other payment providers will be added in the future. The user journey for your customers currently looks like this: 1. Optional: The user opens the Amore checkout link for your product, which will redirect to a pre-configured Stripe Checkout session. 2. The user purchases a Stripe product. Both one-time payments and subscriptions are supported. 3. The user gets redirected to an Amore-hosted success page with information about their purchase and freshly issued license key. 4. The user enters their license key in your app. 5. The license gets validated and your app activated. 6. Optional: The user can manage and revoke their license activations in the Amore customer portal by using their email address as an identifier. You can modify the user journey by pre-issuing license keys or using the licensing API to build your own licensing solution. ### Stripe as Merchant of Record I decided to use Stripe as the first 3rd-party payment provider because they now offer to use Link as a Merchant of Record via [Stripe Managed Payments](https://docs.stripe.com/payments/managed-payments). It's easy to set up and fully compatible with Amore. If you prefer to sell licenses directly via Stripe, you can do that too. Amore itself is already using Stripe Managed Payments since November without any issues or complaints. ### Built with Amore Like last month, I want to thank early supporters and adopters of this project that helped shape Amore into the tool it is today. Here are some of the highlights of the 20 new apps that started relying on Amore since last month. - [*SymbolBrowser*](https://stewartlynch.gumroad.com/l/SymbolBrowser) - [*PrettyTimezones*](https://prettytimezones.com/) - [*Juicy*](https://getjuicy.app/) ### What's Next? As the work on the licensing system is almost complete, I will focus on documentation and help articles to make it easier for new developers to get started. I would like to personally onboard the first developers to AmoreLicensing. If you consider Amore as your licensing system, reply to this email to get in touch. I would love to talk to you. xoxo\ Lucas # How to Distribute a macOS App Outside the App Store Source: https://amore.computer/blog/distribute-macos-app-outside-app-store/ If you're building a Mac app, distributing through the App Store isn't your only option. Many of the most popular Mac apps like ChatGPT, Firefox, Sketch, Sublime Text ship directly to users without ever going through App Review. Here's why you might want to do the same, and exactly how to do it. ## Why distribute outside the App Store? The Mac App Store takes a commission of 30%, or 15% if you are in Apple's Small Business Program. Subscriptions drop to 15% after a subscriber's first year. That's significant, but it's not the only reason developers choose to self-publish: - **No App Review delays.** Ship on your own schedule, not Apple's. - **Direct customer relationship.** You own the relationship and the payment flow. - **Full control.** Use private APIs and system-level features that the App Store doesn't allow. - **No sandboxing requirement.** The App Store requires App Sandbox. Outside it, you choose the security model that fits your app. The trade-off is that you need to handle a few things yourself: code signing, notarization, updates, packaging, and payments. ## Selling on the Mac App Store vs. selling directly If you want to sell on the Mac App Store, the path is: join the [Apple Developer Program](https://developer.apple.com/programs/) ($99 per year), create the app in App Store Connect, accept the Paid Apps Agreement and add tax and banking information, set a price, enable the App Sandbox, upload a build from Xcode, and pass App Review. Apple then handles payments, taxes, and updates. Selling directly means you do those parts yourself, or with tools. Here is how the two compare: | | Mac App Store | Direct (outside the store) | |---|---|---| | Commission | 30%, or 15% in the [Small Business Program](https://developer.apple.com/app-store/small-business-program/) | Payment processor fees, for example [Stripe](https://stripe.com/pricing). A merchant of record such as Stripe Managed Payments adds 3.5% | | Review | App Review for every update | None. Apple notarization is an automated malware check | | Sandbox | Required | Optional | | Payments | StoreKit in-app purchases | Your own checkout, license keys, or [in-app checkout](/blog/in-app-purchases-without-the-mac-app-store/) | | Sales tax and VAT | Apple handles it in over 70 countries and regions | You, or a merchant of record such as [Stripe Managed Payments](https://docs.stripe.com/payments/managed-payments) | | Updates | The App Store | Sparkle | | Customer contact | Apple does not share customer emails | You own the customer list | | Discovery | App Store search and features | Your own website and marketing | You don't have to choose one. Many apps sell in both places: the App Store for discovery, and a direct version for customers who want it, with features the sandbox does not allow. To sell directly, you need licensing. Amore sells license keys through your own Stripe account. See [Mac App Licensing](/licensing/). The rest of this guide covers the technical side of distributing directly. ## What you need Before distributing outside the App Store, you'll need: 1. **An Apple Developer account** ($99/year): required for code signing certificates and notarization. 2. **A Developer ID certificate**: this is different from the App Store distribution certificate. 3. **A way to notarize your app**: Apple requires this so macOS doesn't show scary warnings to your users. 4. **An update mechanism**: your users need a way to get new versions. 5. **A distribution format**: typically a DMG installer. If your app uses capabilities like iCloud or push notifications, you also need a [Developer ID provisioning profile](/help/provisioning-profiles/). ## What it costs | Item | Cost | |---|---| | Apple Developer Program | $99 per year | | Sparkle | Free and open source | | Hosting for the DMG and `appcast.xml` | A few cents per month on S3. [Cloudflare R2](/help/s3-bucket/) has no egress fees | | Payments | [Stripe](https://stripe.com/pricing) fees, or 3.5% more with a merchant of record | | Amore | Free for 2 releases per month and for open-source apps. [Amore+](/pricing/) is $99 per year | ## Code signing and notarization Every Mac app distributed outside the App Store must be signed with a Developer ID certificate and notarized by Apple. Without this, macOS Gatekeeper will block your app or show a warning dialog that scares users away. Notarization is Apple's automated check that your app is free of malware and has been properly signed. The process involves uploading your app to Apple's servers, waiting for approval (usually a few minutes, but up to an hour), and then stapling the notarization ticket to your app. To do it manually, sign with the hardened runtime and a secure timestamp, upload a ZIP to the notary service, and staple the ticket: ```sh codesign --force --options runtime --timestamp \ --sign "Developer ID Application: Your Name (TEAMID)" MyApp.app ditto -c -k --keepParent MyApp.app MyApp.zip xcrun notarytool submit MyApp.zip --keychain-profile "notary" --wait xcrun stapler staple MyApp.app ``` Or [let Amore handle it automatically](/help/codesigning/). If Apple rejects your app, see [Notarization Errors](/help/notarization-errors/). If your certificate is from the original Developer ID authority, read [Developer ID Certificates Expire February 1, 2027](/blog/developer-id-certificate-expires-2027/). ## Over-the-air updates with Sparkle Without the App Store, you need your own update mechanism so your users don’t have to download your app from your website again once you released a new version. [Sparkle](https://sparkle-project.org/) is the de facto standard for macOS app updates. It's been around for over a decade, is used by thousands of apps, and provides a familiar update experience for Mac users. Sparkle works by checking an RSS-like feed (called an `appcast.xml`) for new versions. When an update is available, it downloads it, verifies the cryptographic signature, and installs it, all with a native macOS UI your users already recognize. Setting up Sparkle involves adding the framework to your project, configuring your `Info.plist` with a feed URL and public key, and hosting the appcast somewhere. You can follow our [step-by-step Sparkle setup guide](/help/sparkle/). Keep the private EdDSA key safe: if you lose it, you cannot ship signed updates to existing users. See [Sparkle key management](/help/key-management/). Sandboxed apps need extra setup, see [Sparkle in sandboxed apps](/help/sparkle-sandboxing/). If an update fails, see [Sparkle Errors](/help/sparkle-errors/). ## Creating a DMG installer A DMG (disk image) is the standard way to distribute Mac apps outside the App Store. Users download the DMG, open it, and drag your app to the Applications folder. It's simple and familiar. A good DMG includes a custom background with a visual arrow pointing from your app icon to the Applications folder. The DMG itself should be code-signed and notarized so users don't see any warnings. A basic DMG takes one command. Sign it, then notarize and staple it like the app: ```sh hdiutil create -volname MyApp -srcfolder MyApp.app -ov -format UDZO MyApp.dmg codesign --sign "Developer ID Application: Your Name (TEAMID)" --timestamp MyApp.dmg ``` Setting up the background and icon layout by hand is tedious. [Amore creates DMG installers automatically](/help/dmg-creation/) as part of the release process. ## Testing before you ship Apple does not test your build on other macOS versions for you. Check the oldest macOS version you support in a virtual machine: see [how to test your app on different macOS versions](/help/how-to-test-your-app-on-different-macos-versions/). Also check that your [app icon is the correct size](/help/why-your-mac-app-icon-looks-too-big/) in Finder and the Dock. ## Putting it all together Each piece (code signing, notarization, Sparkle updates, DMG creation) is well-documented individually. The challenge is wiring them all together into a reliable release workflow. That's exactly what [Amore](/) does. It's a Mac app and CLI that handles the entire release pipeline: 1. Code-sign your app with your Developer ID 2. Notarize with Apple 3. Create a DMG installer 4. Sign the update for Sparkle 5. Generate the `appcast.xml` 6. Upload everything for distribution You can do this through the GUI by dragging your app in, or with a single CLI command: ```sh amore release --scheme Acme ``` To release from CI instead of your Mac, use the [GitHub Action](/help/github-actions/). To host updates on your own storage, use [your own S3 bucket](/help/s3-bucket/) and a [custom domain](/help/custom-domain/). [Get started with Amore in under 5 minutes](/help/get-started/). It's free for open-source projects and has a free tier for everyone else. # With Amore №2 Source: https://amore.computer/blog/2/ Hi friends, This is the second issue of »With Amore«, which I plan to use to keep you in the loop about significant project milestones and product updates. Since the first issue 4 weeks ago, I have been working closely with several developers to make Amore easier to use and to support more use cases. Almost every feature now has a dedicated help page on amore.computer which you can find right inside the app. ### WhyFi Last weekend my friend [James Potter](https://jamespotter.dev) shared his weekend project [WhyFi](https://whyfi.network) in a [tweet](https://x.com/jamespotter/status/2014977962890936523), which shortly after went viral. All the attention encouraged him to package it up for distribution and start selling it for $10. On the same day, he reached out to me asking if he could have early access. A few hours later he had hundreds of happy customers and confidently published WhyFi with Amore. Through Amore, he already shipped half a dozen of over-the-air updates to improve WhyFi. His success accomplishes my initial vision for this project, which was to help my friends and fellow developers to focus on what they are doing best: building great products instead of wrangling with tedious distribution flows. ### Public Beta After onboarding new developers to the [self-managed S3 bucket feature](https://amore.computer/help/s3-bucket/) and having the first viral app distributed via Amore, it's time to leave the early access phase and open the project for everyone who's interested to self-publish their Mac apps. You can get started today by downloading Amore from [here](https://amore.computer/download). I recommend you use the [Get Started with Amore](https://amore.computer/help/get-started/) guide, which includes a short [video (3:35)](https://cdn.amore.computer/videos/get-started.mp4) that walks you through the steps of self-publishing your first app with Amore. The option to get a personal onboarding still exists. Please reply to this email if you are interested or need help setting things up. ### Amore+ For the time being Amore will be free to use for everyone who wants to get started publishing their Mac apps. To keep this project sustainable, the latest version of the app includes Amore+, which currently is a monthly/yearly subscription of $9.99/99. The subscription currently unlocks: - Unlimited app and releases - Custom domains (when hosting on Amore) - The option to remove the »Built with amore.computer« watermark in the DMG image ### Built with Amore I want to use this opportunity to thank the early supporters of this project that taught me so much and helped shape Amore into the tool it is today. Their apps are all built with Amore in more ways than one. - *Massimo Biolcati*: [iReal Pro](https://www.irealpro.com/) - *Naveen Naidu*: [Monologue](https://www.monologue.to/) - *James Potter*: [WhyFi](https://whyfi.network/) - *Alberto Gallego*: [Picmal](https://picmal.app/) - *Konstantin*: [Browski](https://iamkonstantin.eu/blog/meet-browski-a-browser-companion-for-the-mac/) ### What's Next? A common theme with developers in onboarding calls has been confusion around some of Amore's features, like [DMG creation](https://amore.computer/help/dmg-creation/), which felt a bit too magical. I am currently working on demystifying and better communicating what Amore is doing and exposing previously implicit behavior as explicit settings in the UI and documenting it. After this work has been done, I want to focus on developer feedback and look into 2 areas more deeply. The first area is payments and licensing for apps, so developers can use Amore to earn money. The second one is to build pretty download pages for apps hosted on Amore and possibly have an index of apps to help developers get more attention for their projects. xoxo\ Lucas # With Amore №1 Source: https://amore.computer/blog/1/ Hi friends, This is the first issue of »With Amore«, which I plan to use to keep you in the loop about significant project milestones and product updates. Since my [blog post](https://lucas.love/blog/vision-macos-app-distribution) back in late November the project evolved into a fully-fledged Mac app that's able to take care of every step of distributing app updates to your users and already has a couple of developers using it. Last week I introduced a new [help page](https://amore.computer/help/) that hosts Amore's documentation and shows how to get started. It's a good way to get an overview of what Amore is currently capable of. I strive to make this one of the best documentations for a developer tool. Let's dive into some of the things Amore can do for you today. ### From Archiving to Releasing To streamline the release process, _Amore_'s [cli](https://amore.computer/help/command-line/) offers a command to be used with [Xcode' archive post-actions](https://amore.computer/help/xcode-post-archive-action/), which will automatically handle every part of the release process after you archived your project. This includes: 1. Exporting the archive 2. Code signing your executable 3. Creating an installer DMG (optional) 4. Notarizing your app 5. Signing your app for the Sparkle updater 6. Creating a new `appcast.xml` 7. Uploading your app binary ### Amore for New Apps The help pages now include a [Get Started](https://amore.computer/help/get-started/) article that describes how to configure a new app for distribution via Amore. The process consists of 3 easy steps: 1. Register app with Amore via drag and drop 2. Configure Sparkle in your project. (Amore can do parts of this automatically) 3. Publish your first release I tested this with multiple developers now and the whole process takes around 10 minutes. ### Amore for Existing Apps Over the last month I did dozens of developer onboarding sessions and learned that developers with existing apps face different problems and would like to keep their existing Sparkle setups. Most of these setups included distribution of updates via the developer's S3 bucket and some custom scripts to create notarized DMGs and upload the result to S3. I am happy to report that Amore now supports the same convenience for apps distributed via S3 buckets. You will be able to use your own S3 bucket from any cloud provider to distribute updates. Only minimal configuration will be required. You won't need to change the source code of your app to get started with Amore. Once setup, Amore can take care of the full release pipeline and you can even edit the `appcast.xml` right from inside Amore. This means you can tweak release notes or change settings like beta channel or phased rollouts without ever leaving Amore. ### Early Access Promo As a thank you for your early support, I offer you the sexy price of $69 per year instead of $99, which won't change, even when I raise subscription prices in the future. You can get it [here](https://amore.computer/early-access-promo). ### What's Next? Currently I am working on the public beta release of Amore and looking for a few developers who are interested in personal onboarding sessions. If that is something that interests you, please reply to this email to get in touch. xoxo\ Lucas