#!/bin/bash # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (c) 2025-2026 Cristian Cezar Moisés # # Build a macOS .dmg installer for the Zupt CLI. # # This script MUST be run on macOS — `hdiutil` is required and only # ships with macOS. There is no portable way to produce a .dmg from # Linux that Apple's installer will mount cleanly (libdmg-hfsplus and # dmg2img exist but produce read-only images that some macOS versions # reject). # # On macOS: # xcode-select --install # one-time, for clang # make # build the zupt binary # VERSION=2.4.7 bash packaging/build-dmg.sh # # Produces: /tmp/Zupt-VERSION.dmg with: # - zupt binary (universal2 if built with -arch x86_64 -arch arm64) # - libzuptsdk dylib alongside the binary at @loader_path # - install.command (drag-to-install script) # - README.md, LICENSE # - Optional: code-signed and notarized if APPLE_DEV_ID env is set # # For Homebrew installation, prefer packaging/homebrew/zupt.rb instead. # The .dmg is for users who don't want to install Homebrew. set -e cd "$(dirname "$0")/.." VERSION="${VERSION:-2.4.7}" ARCH="${ARCH:-$(uname -m)}" # x86_64 or arm64 NAME="Zupt-${VERSION}-${ARCH}" STAGE="/tmp/${NAME}.app/Contents" # ── Platform check ── if [ "$(uname)" != "Darwin" ]; then cat >&2 <&2 < "$STAGE/Info.plist" < CFBundleIdentifier co.securityops.zupt CFBundleName Zupt CFBundleDisplayName Zupt CFBundleVersion ${VERSION} CFBundleShortVersionString ${VERSION} CFBundleExecutable zupt CFBundlePackageType APPL NSHighResolutionCapable LSMinimumSystemVersion 11.0 PLIST cp README.md "$STAGE/Resources/" 2>/dev/null || true cp LICENSE "$STAGE/Resources/" 2>/dev/null || true # ── Drag-to-install command file ── cat > "/tmp/${NAME}-install.command" <<'INSTALL' #!/bin/bash # Drag-installer for Zupt CLI. Copies the binary to /usr/local/bin # (or the user's ~/bin if /usr/local isn't writable). set -e DIR="$(cd "$(dirname "$0")" && pwd)" APP="$DIR/Zupt.app" TARGET="/usr/local/bin" if [ ! -w "$TARGET" ]; then TARGET="$HOME/bin" mkdir -p "$TARGET" echo "Installing to $TARGET (add to PATH if missing)" fi cp "$APP/Contents/MacOS/zupt" "$TARGET/zupt" chmod 755 "$TARGET/zupt" # Bundle the dylib alongside under a stable path LIBDIR="/usr/local/lib/zupt" [ -w /usr/local/lib ] || LIBDIR="$HOME/lib/zupt" mkdir -p "$LIBDIR" if [ -d "$APP/Contents/Frameworks" ]; then cp -P "$APP/Contents/Frameworks"/* "$LIBDIR/" 2>/dev/null || true fi echo "Installed: $TARGET/zupt" "$TARGET/zupt" version INSTALL chmod 755 "/tmp/${NAME}-install.command" # ── Optional: code sign ── if [ -n "${APPLE_DEV_ID:-}" ]; then echo "[dmg] Code-signing with Developer ID: $APPLE_DEV_ID" codesign --force --options runtime --sign "$APPLE_DEV_ID" \ --entitlements packaging/macos/entitlements.plist \ "$STAGE/MacOS/zupt" 2>&1 || echo " (no entitlements file — proceeding unsigned for hardening)" codesign --force --sign "$APPLE_DEV_ID" "/tmp/${NAME}.app" || true fi # ── Build .dmg ── echo "[dmg] Building disk image" DMG="/tmp/${NAME}.dmg" rm -f "$DMG" # Stage a directory tree that becomes the .dmg root DMGSRC="/tmp/${NAME}-dmgsrc" rm -rf "$DMGSRC" mkdir -p "$DMGSRC" cp -R "/tmp/${NAME}.app" "$DMGSRC/Zupt.app" cp "/tmp/${NAME}-install.command" "$DMGSRC/Install Zupt.command" [ -f README.md ] && cp README.md "$DMGSRC/" [ -f LICENSE ] && cp LICENSE "$DMGSRC/" hdiutil create -fs HFS+ -srcfolder "$DMGSRC" -volname "Zupt ${VERSION}" \ -format UDZO -ov "$DMG" # ── Optional: notarize ── if [ -n "${APPLE_DEV_ID:-}" ] && [ -n "${APPLE_NOTARIZE_KEY:-}" ]; then echo "[dmg] Submitting for notarization" xcrun notarytool submit "$DMG" --apple-id "$APPLE_DEV_ID" \ --password "$APPLE_NOTARIZE_KEY" --wait xcrun stapler staple "$DMG" fi echo "" echo "Built: $DMG ($(du -h "$DMG" | cut -f1))" echo "Users mount and drag 'Zupt.app' or double-click 'Install Zupt.command'."