# macOS Bundle Signing and DMG Packaging Workflow in OpenLogi Releases

> Discover the OpenLogi workflow for macOS bundle signing and DMG packaging. Learn how to build, sign, and package your app for production releases with this detailed guide.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-09-05

---

**OpenLogi orchestrates macOS distribution through a two-stage `xtask` workflow that first builds and signs the app bundle with a production channel identity, then packages it into a branded, ULMO-compressed DMG with optional final code signing.**

The macOS bundle signing and DMG packaging workflow in OpenLogi is managed entirely through the `xtask` command-line utility located in the `AprilNEA/OpenLogi` repository. This system ensures that every distributed copy carries the correct **Production** channel identity required for macOS Transparency, Consent, and Control (TCC) permissions, while supporting both signed releases and unsigned local development builds.

## Stage 1: Building and Signing the App Bundle

The first stage creates the `.app` structure and applies the necessary identity metadata before any cryptographic signing occurs.

### Bundle Creation and Channel Identity

The command `cargo xtask macos bundle` generates the application bundle at `target/release/bundle/osx/OpenLogi.app`. Before signing, the utility invokes the identity stamping logic in [`xtask/src/commands/macos/bundle/identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/bundle/identity.rs) to ensure the bundle carries a stable **Production** identity.

The `stamp` function in [`identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/identity.rs) writes the following critical values to `Info.plist`:

- **CFBundleIdentifier**: A stable reverse-domain identifier used by macOS to track permission grants across updates
- **CFBundleName**: The display name presented to users in TCC dialogs

According to the source code comments in [`identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/identity.rs) (lines 4-8), applying this production identity *before* signing is mandatory. If a developer were to sign a bundle with a development identity first, existing user permissions would break when the app updates, as macOS treats different identities as distinct applications.

### Code Signing the Bundle

When the `OPENLOGI_SIGN_IDENTITY` environment variable or `--sign-identity` flag is provided, the `xtask` utility passes the certificate to the `codesign` tool:

```bash
export OPENLOGI_SIGN_IDENTITY="Developer ID Application: Jane Doe (ABCD1234)"
cargo xtask macos bundle --channel production

```

The `verify` function in [`identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/identity.rs) ensures every nested component—including the Agent helper and Overlay helper—carries the same signing identity before the process completes. This satisfies Apple's "sign-inside-out" requirement where all bundled executables must share the same designated requirement.

If no signing identity is supplied, the bundle builds unsigned, which is useful for local testing:

```bash
cargo xtask macos bundle  # Creates unsigned dev bundle

```

## Stage 2: Packaging the Signed Bundle into a DMG

Once the signed bundle exists, the second stage creates the distribution artifact.

### DMG Creation with create-dmg

The `cargo xtask macos dmg` command (implemented in [`xtask/src/commands/macos/dmg.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/dmg.rs), lines 31-80) performs the following actions:

1. Downloads a branded background image from `https://assets.openlogi.org/dmg/dmg-background.tiff`
2. Invokes the external `create-dmg` tool to generate a **ULMO-compressed** disk image
3. Configures Finder window geometry and positions the app icon on the background image

The command accepts the bundle path and output location:

```bash
cargo xtask macos dmg \
    --app target/release/bundle/osx/OpenLogi.app \
    --output target/release/OpenLogi.dmg \
    --sign-identity "$OPENLOGI_SIGN_IDENTITY"

```

### Signing the Distribution Image

If a signing identity was provided to the bundle stage, the same identity must sign the final DMG. The `sign_dmg` function in [`dmg.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/dmg.rs) (lines 91-96) executes `codesign --force` against the completed disk image. This creates a consistent cryptographic chain from the nested helpers through the app bundle to the outer DMG container.

Unsigned DMG creation remains available for ad-hoc testing by omitting the `--sign-identity` flag:

```bash
cargo xtask macos dmg  # Creates unsigned DMG for local testing

```

## CI/CD Automation

The release pipeline defined in [`.github/workflows/release.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/workflows/release.yml) automates this two-stage workflow for official distributions:

```yaml
- name: Build production bundle
  run: cargo xtask macos bundle --channel production

- name: Package DMG
  run: cargo xtask macos dmg --sign-identity "$OPENLOGI_SIGN_IDENTITY"

```

The CI job reads the `OPENLOGI_SIGN_IDENTITY` secret from repository settings. If the identity is unset, the workflow produces unsigned artifacts suitable for testing but blocks production release gates.

## Local Development vs. Production Releases

Understanding the distinction between ad-hoc and production builds prevents permission corruption on developer machines:

- **Production builds** require the `--channel production` flag, which triggers the `stamp` function to inject the stable `CFBundleIdentifier`. These builds must be signed with a valid Apple Developer ID for distribution outside the App Store.
- **Development builds** omit the channel flag, resulting in a dynamic bundle identifier. These never carry a signing identity and cannot overwrite production app permissions in macOS TCC databases.

## Summary

- **Two-stage workflow**: First `cargo xtask macos bundle` creates and signs the app, then `cargo xtask macos dmg` packages it into a distributable disk image
- **Identity stamping**: The production channel identity must be applied in [`xtask/src/commands/macos/bundle/identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/bundle/identity.rs) before signing to preserve user permissions
- **Sign-inside-out**: All nested helpers, the main app, and the final DMG must share the same signing identity, verified by the `verify` function
- **Compression**: DMGs use ULMO compression via the `create-dmg` tool with branded backgrounds fetched from OpenLogi assets
- **Environment control**: The `OPENLOGI_SIGN_IDENTITY` variable controls whether artifacts are signed or left unsigned for testing

## Frequently Asked Questions

### What happens if OPENLOGI_SIGN_IDENTITY is not set?

When the `OPENLOGI_SIGN_IDENTITY` environment variable and `--sign-identity` flag are both absent, both the `bundle` and `dmg` commands produce unsigned artifacts. The bundle builds normally but skips the `codesign` invocation, and the DMG packages the unsigned bundle without cryptographic sealing. This configuration is intended strictly for local development and CI testing pipelines.

### Why must the production identity be stamped before signing?

According to the source code in [`xtask/src/commands/macos/bundle/identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/bundle/identity.rs), macOS TCC (Transparency, Consent, and Control) permissions are bound to the `CFBundleIdentifier`. If a developer signs a bundle with a development identity first, macOS records permissions against that temporary identifier. When the app later updates with the production identity, macOS treats it as a completely different application, invalidating all previous user grants for microphone, screen recording, and accessibility access.

### What is the "sign-inside-out" requirement?

Apple's signing architecture requires that every executable inside an app bundle—from the main binary to helper tools in `Contents/Library/LoginItems/`—carries the same cryptographic identity as the bundle itself. The `verify` function in [`identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/identity.rs) recursively checks all nested components, and the CI workflow aborts if any component lacks the production signature. This prevents "identifier collision" errors during notarization.

### Can I customize the DMG background image?

Yes, though the default fetches from `https://assets.openlogi.org/dmg/dmg-background.tiff`. The [`dmg.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/dmg.rs) implementation supports passing a custom background path to the underlying `create-dmg` invocation. For branded enterprise releases, modify the arguments passed to the background configuration in [`xtask/src/commands/macos/dmg.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/dmg.rs) or replace the asset at the remote URL if using the default fetch behavior.