Files
nucleic/scripts/make-dmg.sh
T
2026-07-02 20:38:28 -07:00

77 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Build a Developer-ID-signed DMG from an already-built channel .app.
#
# scripts/make-dmg.sh {dev|canary|beta|rc|stable}
#
# Expects dist/<App Name>.app to exist (run scripts/package-app.sh <channel> first).
# Produces a compressed DMG with the standard drag-to-/Applications layout, signed
# with NUCLEIC_SIGN_ID (the DMG itself is signed so Gatekeeper trusts the container;
# notarization of the DMG is a separate step — see scripts/notarize.sh). The DMG file
# name is hyphenated (no spaces) so it works cleanly as a GitHub Release asset and a
# Sparkle appcast URL; the mounted volume name keeps the human-friendly spaced name.
set -euo pipefail
CHANNEL="${1:?usage: make-dmg.sh (dev|canary|beta|rc|stable)}"
SIGN_ID="${NUCLEIC_SIGN_ID:--}"
case "$CHANNEL" in
dev) APP_NAME="Nucleic Dev"; SLUG="Nucleic-Dev" ;;
canary) APP_NAME="Nucleic Canary"; SLUG="Nucleic-Canary" ;;
beta) APP_NAME="Nucleic Beta"; SLUG="Nucleic-Beta" ;;
rc) APP_NAME="Nucleic RC"; SLUG="Nucleic-RC" ;;
stable) APP_NAME="Nucleic"; SLUG="Nucleic" ;;
*) echo "usage: $0 {dev|canary|beta|rc|stable}" >&2; exit 2 ;;
esac
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
APP="$ROOT/dist/$APP_NAME.app"
[ -d "$APP" ] || { echo "make-dmg: $APP not found — run scripts/package-app.sh $CHANNEL first" >&2; exit 1; }
# Version comes from the bundle so the DMG name matches what package-app.sh stamped.
plist="$APP/Contents/Info.plist"
SHORT_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$plist")"
BUILD_NUMBER="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$plist")"
DMG="$ROOT/dist/${SLUG}-${SHORT_VERSION}.${BUILD_NUMBER}.dmg"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
echo "▸ Staging $APP_NAME.app for DMG"
cp -R "$APP" "$STAGE/"
ln -s /Applications "$STAGE/Applications"
echo "▸ Building DMG → $DMG"
rm -f "$DMG"
# Build a compressed (UDZO), read-only DMG from the staged folder. On macOS 26+ (Tahoe) use the
# modern `diskutil image create from`: it packs the folder directly and performs its internal
# mount through the non-deprecated path, so there's no `hdiutil attach -mountpoint -nobrowse`
# deprecation warning (hdiutil's own `create`/`attach` are deprecated there too). Older macOS
# lacks the `diskutil image` verb, so fall back to the classic one-shot hdiutil there.
if [ "$(sw_vers -productVersion | cut -d. -f1)" -ge 26 ]; then
diskutil image create from \
--format UDZO \
--volumeName "$APP_NAME" \
"$STAGE" "$DMG" >/dev/null
else
hdiutil create \
-volname "$APP_NAME" \
-srcfolder "$STAGE" \
-fs HFS+ \
-format UDZO \
-ov \
"$DMG" >/dev/null
fi
# Sign the DMG container itself (a real identity gets a secure timestamp; ad-hoc can't).
if [ "$SIGN_ID" = "-" ]; then
codesign --force --timestamp=none --sign - "$DMG"
echo "✓ $DMG (ad-hoc — local only)"
else
codesign --force --timestamp --sign "$SIGN_ID" "$DMG"
codesign --verify --verbose=1 "$DMG"
echo "✓ $DMG (signed: $SIGN_ID)"
fi
echo " notarize next: scripts/notarize.sh \"$DMG\""