๐Ÿšง Early alpha Under active construction. Everything here may change without notice โ€” except the appcast URL. That one's forever.

Aetower

Docs

Direct Download Release

Aetower's current public release channel is Developer Preview. Public artifacts should be distributed only through the signed and notarized direct-download path.

The direct-download release path assumes:

Required environment

Set these before running the release scripts:

Optional:

One-time setup

  1. Install a Developer ID Application certificate in Keychain (Xcode โ†’ Settings โ†’ Accounts โ†’ Manage Certificates โ†’ + โ†’ Developer ID Application). This is required: Sparkle-delivered updates must be Developer ID signed AND notarized or Gatekeeper will refuse to launch them.
  1. Store notarization credentials:

   sh scripts/store-notary-credentials.sh <profile-name> <apple-id> <team-id>
  1. Generate the Sparkle EdDSA key pair (after the Sparkle package has been resolved by a build at least once):

   ./macos/.build/artifacts/sparkle/Sparkle/bin/generate_keys

This stores the private key in the Keychain and prints the public key. Copy the public key into AETOWER_SPARKLE_PUBLIC_ED_KEY. Back up the private key somewhere safe โ€” if it is lost, no client can verify any future update.

  1. Install a Developer ID Installer certificate in Keychain if public PKG output is expected.
  1. Choose hosting for appcast.xml and the release .zip, .dmg, and .pkg files (Cloudflare Pages, GitHub Releases, a static site, S3, etc.) and set AETOWER_APPCAST_URL (and, if the zips are hosted apart from the appcast, AETOWER_DOWNLOAD_URL_PREFIX).

Cut a Developer Preview release

Run the public pipeline:


sh scripts/release-public-preview.sh

This runs, in order:

  1. scripts/release-preflight.sh โ€” verifies signing identity, notary profile, appcast URL, public key, and final release metadata are present.
  2. scripts/package-macos.sh โ€” builds, embeds + inside-out signs Sparkle.framework, generates and embeds the app icon, code-signs with the hardened runtime, notarizes, and (optionally) staples. Produces dist/Aetower.app and dist/Aetower.zip.
  3. scripts/generate-sparkle-appcast.sh โ€” copies the zip into the archives directory (dist/appcast/ by default) under a version-and-build-specific filename and runs Sparkle's generate_appcast to (re)write dist/appcast/appcast.xml, signing each entry with the EdDSA key and applying the download URL prefix.
  4. scripts/generate-source-archive.sh โ€” generates the versioned corresponding source archive from the clean release commit.
  5. scripts/package-macos-dmg.sh โ€” builds a signed/notarized drag-and-drop DMG with the Aetower.app icon, /Applications symlink, and branded install background.
  6. scripts/package-macos-pkg.sh โ€” builds a signed/notarized installer PKG for installer and managed deployment workflows.
  7. scripts/generate-third-party-notices.sh โ€” generates dist/THIRD-PARTY-NOTICES.md from the locked Rust Cargo graph and SwiftPM package resolution.
  8. scripts/verify-sparkle-distribution-matrix.sh --require-dmg --require-pkg โ€” verifies ZIP, appcast, Homebrew cask, DMG, and PKG all resolve to the same Sparkle-enabled bundle.
  9. scripts/prepare-release-payload.sh โ€” stages the release payload (dist/releases-payload), syncing the currently-published history first.
  10. scripts/prepare-cloudflare-site.sh โ€” stages the website (dist/cloudflare-site), which deploys separately from the payload.
  11. Optional scripts/publish-homebrew-tap.sh โ€” publishes the generated cask to the real tap repository when --publish-homebrew-tap is set.

The individual scripts can also be run directly with the same environment.

Public Preview Orchestrator

Use the full local release set command when preparing a public preview:


sh scripts/release-public-preview.sh

This runs the signed/notarized macOS ZIP release, generates the Sparkle appcast, generates the Homebrew cask artifact, generates the corresponding source archive, generates signed/notarized DMG and PKG installers, generates third-party dependency/license notices, verifies the Sparkle distribution matrix, and prepares the Cloudflare Pages payload. It does not publish to Cloudflare or the Homebrew tap by default.

The generated cask becomes a real Homebrew distribution only after it is pushed to the Aeptus/homebrew-aetower tap. Publish that tap explicitly; see Homebrew Release.

The .dmg is the default human download. The .pkg is for installer-style distribution and MDM/admin workflows. Sparkle updates still work after either install path because Sparkle updates the installed .app. The release feed still uses the ZIP appcast because this repo's Sparkle generate_appcast tool does not support package-based update archives.

The release pipeline verifies that ZIP, Homebrew cask, DMG, and PKG all resolve to the same Sparkle-enabled bundle. Run the matrix directly when debugging packaging issues:


sh scripts/verify-sparkle-distribution-matrix.sh
sh scripts/verify-sparkle-distribution-matrix.sh --require-dmg --require-pkg

The matrix is a release-artifact check. A local development package built without AETOWER_APPCAST_URL and AETOWER_SPARKLE_PUBLIC_ED_KEY is expected to fail because Sparkle is intentionally disabled for that artifact.

Publish explicitly only when the generated build should become visible to Sparkle clients:


sh scripts/release-public-preview.sh --prepare-only --publish-cloudflare

The publish path deploys the prepared Cloudflare Pages payload, then verifies that the public appcast contains the expected AETOWER_VERSION and AETOWER_BUILD_NUMBER, the immutable Sparkle archive resolves, the direct ZIP resolves, the DMG and PKG resolve, the corresponding source archive resolves, and the generated third-party notices are public. Use --skip-public-verify only when Cloudflare propagation is being checked manually.

When the public URLs verify, publish the matching Homebrew tap in the same run:


sh scripts/release-public-preview.sh --prepare-only --publish-cloudflare --publish-homebrew-tap

If Cloudflare is already current and only the tap needs repair, run:


sh scripts/release-public-preview.sh --prepare-only --publish-homebrew-tap

Publish

Upload the prepared release payload (dist/releases-payload/) or equivalent host payload; the website (dist/cloudflare-site/) deploys separately via scripts/deploy-website.sh. At minimum, publish the appcast archives (appcast.xml, Aetower-<version>-<build>.zip, and any generated deltas), the latest DMG, the latest PKG, source archives, notices, and the generated Homebrew cask mirror so that:

Then publish Aeptus/homebrew-aetower so Homebrew users get the matching cask through brew tap aeptus/aetower.

Keep the archives directory between releases (don't wipe it): generate_appcast re-uses the existing appcast.xml and prior archives to build delta updates and a growing version history.

Verify the update flow (do this before trusting a release)

  1. Build and notarize the previous version (N-1) and install it.
  2. Publish the appcast/archives for version N.
  3. In the app: Check for Updates (menu or Settings โ†’ Updates). Confirm it detects N, downloads, passes EdDSA verification, installs, and relaunches into N. This is the only step that proves the cert, EdDSA keys, version numbering, and appcast URLs all line up.

Direct-download baseline

The baseline release path does not require Endpoint Security. Leave the privileged helper disabled unless you are shipping an advanced build with the restricted entitlement approved by Apple.

Use Developer Preview Release Checklist as the final human checklist before publishing an artifact.

If publishing through Cloudflare Pages on aetower.dev, use Cloudflare Release Hosting.

Aetower is a free early-alpha download for macOS 14+ (Apple silicon).

Download for macOS