mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
fix(release): rebuild DMG from scratch via bundle_dmg.sh (supersedes #952/#955) (#957)
This commit is contained in:
@@ -8,107 +8,124 @@
|
||||
# APPLE_ID
|
||||
# APPLE_PASSWORD (app-specific password)
|
||||
# APPLE_TEAM_ID
|
||||
# Re-packaging involves:
|
||||
# 1. Converting the original DMG (with correct layout/background/DS_Store) to writable.
|
||||
# 2. Resizing it to ensure enough space.
|
||||
# 3. Replacing the original .app with the notarized one using ditto.
|
||||
# 4. Converting back to compressed format and notarizing.
|
||||
#
|
||||
# Assets used:
|
||||
# - app/src-tauri/images/background-dmg.png (baked into the original DMG)
|
||||
# - /Applications symlink (baked into the original DMG)
|
||||
# Why a full rebuild instead of mount-and-replace:
|
||||
#
|
||||
# The previous implementation converted the original Tauri-built UDZO DMG to
|
||||
# UDRW, mounted it, replaced the .app with the notarized one, unmounted,
|
||||
# and converted back to UDZO. That round-trip fails consistently on macOS
|
||||
# 26.x runners with `hdiutil: convert failed - internal error` immediately
|
||||
# after "Preparing imaging engine…". The failure is structural — modifying a
|
||||
# UDZO→UDRW image and re-compressing it is broken in current hdiutil.
|
||||
# Tauri's own bundle_dmg.sh builds a fresh UDRW from a source folder via
|
||||
# `hdiutil create -srcfolder` and then converts to UDZO; that path works.
|
||||
#
|
||||
# So instead of round-tripping, we reuse Tauri's vendored bundle_dmg.sh
|
||||
# (which is already on disk in `<bundle_dir>/dmg/bundle_dmg.sh` from the
|
||||
# original tauri-build step) and rebuild the DMG from scratch around the
|
||||
# now-notarized .app. The output DMG has the same layout (background,
|
||||
# /Applications symlink, icon positions) as the original.
|
||||
set -euo pipefail
|
||||
|
||||
APP_PATH="${1:?Usage: repackage-dmg.sh <app_path> <bundle_dir>}"
|
||||
BUNDLE_DIR="${2:?}"
|
||||
|
||||
DMG_PATH="$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -maxdepth 1 2>/dev/null | head -1)"
|
||||
if [ -z "$DMG_PATH" ]; then
|
||||
echo "[dmg] No DMG found — skipping DMG re-package"
|
||||
# Resolve all bundle paths to absolute form — we cd into $MACOS_DIR below
|
||||
# to invoke bundle_dmg.sh, and relative paths would break after the cd.
|
||||
BUNDLE_DIR_ABS="$(cd "$BUNDLE_DIR" && pwd)"
|
||||
DMG_DIR="$BUNDLE_DIR_ABS/dmg"
|
||||
MACOS_DIR="$BUNDLE_DIR_ABS/macos"
|
||||
BUNDLE_SCRIPT="$DMG_DIR/bundle_dmg.sh"
|
||||
SUPPORT_DIR="$BUNDLE_DIR_ABS/share/create-dmg/support"
|
||||
|
||||
if [ ! -x "$BUNDLE_SCRIPT" ]; then
|
||||
echo "[dmg] ERROR: bundle_dmg.sh not found at $BUNDLE_SCRIPT" >&2
|
||||
echo "[dmg] Did the original tauri-build step run successfully?" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$SUPPORT_DIR" ]; then
|
||||
echo "[dmg] ERROR: support dir not found at $SUPPORT_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "[dmg] ERROR: app bundle not found at $APP_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
APP_PATH_ABS="$(cd "$APP_PATH/.." && pwd)/$(basename "$APP_PATH")"
|
||||
APP_NAME="$(basename "$APP_PATH")"
|
||||
|
||||
# The .app must be inside $MACOS_DIR for the bundle_dmg.sh srcfolder arg.
|
||||
# If the caller passed an .app from a different location, copy it into
|
||||
# place so bundle_dmg.sh picks up the right (notarized) bundle.
|
||||
if [ "$APP_PATH_ABS" != "$MACOS_DIR/$APP_NAME" ]; then
|
||||
echo "[dmg] Staging $APP_NAME into $MACOS_DIR"
|
||||
rm -rf "$MACOS_DIR/$APP_NAME"
|
||||
ditto "$APP_PATH_ABS" "$MACOS_DIR/$APP_NAME"
|
||||
fi
|
||||
|
||||
# Capture the existing DMG name so the rebuild outputs to the same path.
|
||||
# tauri-build always produces exactly one .dmg per target.
|
||||
ORIGINAL_DMG="$(find "$DMG_DIR" -maxdepth 1 -name '*.dmg' ! -name 'rw.*.dmg' -type f 2>/dev/null | head -1 || true)"
|
||||
if [ -z "$ORIGINAL_DMG" ]; then
|
||||
echo "[dmg] No DMG found in $DMG_DIR — nothing to repackage" >&2
|
||||
exit 0
|
||||
fi
|
||||
DMG_NAME="$(basename "$ORIGINAL_DMG")"
|
||||
FINAL_DMG="$DMG_DIR/$DMG_NAME"
|
||||
echo "[dmg] Rebuilding $DMG_NAME from notarized $APP_NAME"
|
||||
|
||||
# Background image — same one Tauri uses (declared in app/src-tauri/tauri.conf.json).
|
||||
# Allow override via env so callers (or tests) can point elsewhere.
|
||||
BACKGROUND_PATH="${DMG_BACKGROUND_PATH:-app/src-tauri/images/background-dmg.png}"
|
||||
if [ ! -f "$BACKGROUND_PATH" ]; then
|
||||
echo "[dmg] WARNING: background image not found at $BACKGROUND_PATH — building without background" >&2
|
||||
BACKGROUND_PATH=""
|
||||
fi
|
||||
|
||||
# ── Cleanup ──────────────────────────────────────────────────────────────────
|
||||
# Clean up temporary files and unmount images on exit
|
||||
cleanup() {
|
||||
set +e
|
||||
if [ -n "${VERIFY_MOUNT:-}" ] && [ -d "$VERIFY_MOUNT" ]; then
|
||||
echo "[dmg] Cleaning up verification mount..."
|
||||
hdiutil detach "$VERIFY_MOUNT" -force 2>/dev/null || true
|
||||
rmdir "$VERIFY_MOUNT" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "${MOUNT_DIR:-}" ] && [ -d "$MOUNT_DIR" ]; then
|
||||
echo "[dmg] Cleaning up rebuild mount..."
|
||||
hdiutil detach "$MOUNT_DIR" -force 2>/dev/null || true
|
||||
rmdir "$MOUNT_DIR" 2>/dev/null || true
|
||||
fi
|
||||
if [ -f "${DMG_RW:-}" ]; then
|
||||
rm -f "$DMG_RW"
|
||||
fi
|
||||
# bundle_dmg.sh writes scratch files alongside the output — clean any leftovers.
|
||||
find "$DMG_DIR" -maxdepth 1 -name 'rw.*.dmg' -delete 2>/dev/null || true
|
||||
find "$MACOS_DIR" -maxdepth 1 -name 'rw.*.dmg' -delete 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "[dmg] Re-packaging DMG to preserve layout (background, icons, symlinks)..."
|
||||
# 1. Convert the original Tauri-generated DMG to a writable format (UDRW)
|
||||
# Note: XXXXXX must be at the end of the template for BSD mktemp (macOS).
|
||||
# We append .dmg to ensure hdiutil doesn't add it implicitly, causing mismatch.
|
||||
DMG_RW="$(mktemp /tmp/OpenHuman-RW-XXXXXX).dmg"
|
||||
hdiutil convert "$DMG_PATH" -format UDRW -ov -o "$DMG_RW"
|
||||
# Pre-clean any leftover scratch DMGs from prior failed runs.
|
||||
find "$DMG_DIR" -maxdepth 1 -name 'rw.*.dmg' -delete 2>/dev/null || true
|
||||
find "$MACOS_DIR" -maxdepth 1 -name 'rw.*.dmg' -delete 2>/dev/null || true
|
||||
rm -f "$FINAL_DMG"
|
||||
|
||||
# 2. Resize and replace the app
|
||||
# Increase size to ensure the notarized bundle fits (may be slightly larger due to stapling)
|
||||
hdiutil resize -size 1g "$DMG_RW"
|
||||
BUNDLE_ARGS=(
|
||||
--volname "OpenHuman"
|
||||
--icon "$APP_NAME" 180 170
|
||||
--app-drop-link 480 170
|
||||
--window-size 660 400
|
||||
--hide-extension "$APP_NAME"
|
||||
--skip-jenkins
|
||||
)
|
||||
if [ -n "$BACKGROUND_PATH" ]; then
|
||||
BACKGROUND_ABS="$(cd "$(dirname "$BACKGROUND_PATH")" && pwd)/$(basename "$BACKGROUND_PATH")"
|
||||
BUNDLE_ARGS+=(--background "$BACKGROUND_ABS")
|
||||
fi
|
||||
|
||||
# Mount the writable image using a temporary directory
|
||||
MOUNT_DIR="$(mktemp -d /tmp/OpenHuman-Rebuild-XXXXXX)"
|
||||
hdiutil attach "$DMG_RW" -mountpoint "$MOUNT_DIR" -noautoopen
|
||||
echo "[dmg] Running bundle_dmg.sh..."
|
||||
(
|
||||
cd "$MACOS_DIR"
|
||||
bash "$BUNDLE_SCRIPT" "${BUNDLE_ARGS[@]}" "$FINAL_DMG" "$APP_NAME"
|
||||
)
|
||||
|
||||
# Replace the non-notarized app with the notarized one
|
||||
# We use ditto to preserve all metadata and handles the .app bundle correctly
|
||||
APP_NAME="$(basename "$APP_PATH")"
|
||||
rm -rf "$MOUNT_DIR/$APP_NAME"
|
||||
ditto "$APP_PATH" "$MOUNT_DIR/$APP_NAME"
|
||||
if [ ! -f "$FINAL_DMG" ]; then
|
||||
echo "[dmg] ERROR: bundle_dmg.sh did not produce $FINAL_DMG" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[dmg] Built fresh DMG at $FINAL_DMG ($(du -h "$FINAL_DMG" | cut -f1))"
|
||||
|
||||
# Unmount. `-force` plus a sync + brief settle window prevents the
|
||||
# intermittent "hdiutil: convert failed - internal error" that fires when
|
||||
# the writable image's resource handles are still being released.
|
||||
hdiutil detach "$MOUNT_DIR" -force
|
||||
rmdir "$MOUNT_DIR"
|
||||
MOUNT_DIR=""
|
||||
sync
|
||||
sleep 2
|
||||
|
||||
# 3. Compact the writable image to reclaim the empty space left over from
|
||||
# the 1GB resize. Without this the UDZO output carries hundreds of MB of
|
||||
# zero blocks and `hdiutil convert` is more likely to fail.
|
||||
echo "[dmg] Compacting writable image before recompression..."
|
||||
hdiutil compact "$DMG_RW" || echo "[dmg] hdiutil compact returned non-zero (continuing)"
|
||||
|
||||
# 4. Convert back to compressed format (UDZO).
|
||||
# Convert to a TEMPORARY path and `mv` over the original instead of
|
||||
# `-ov`-overwriting the input. On macOS GitHub runners the in-place
|
||||
# overwrite fails immediately with "hdiutil: convert failed - internal
|
||||
# error" — the source DMG handle is still held by the imaging engine when
|
||||
# the same path is reopened for write. A separate output path sidesteps
|
||||
# the issue entirely.
|
||||
DMG_OUT="$(mktemp /tmp/OpenHuman-UDZO-XXXXXX).dmg"
|
||||
rm -f "$DMG_OUT" # mktemp created an empty file; hdiutil refuses to overwrite without -ov.
|
||||
convert_attempts=0
|
||||
until hdiutil convert "$DMG_RW" -format UDZO -o "$DMG_OUT"; do
|
||||
convert_attempts=$((convert_attempts + 1))
|
||||
if [ "$convert_attempts" -ge 3 ]; then
|
||||
echo "[dmg] ERROR: hdiutil convert to UDZO failed after $convert_attempts attempts" >&2
|
||||
rm -f "$DMG_OUT"
|
||||
exit 1
|
||||
fi
|
||||
echo "[dmg] hdiutil convert failed (attempt $convert_attempts) — retrying after settle..." >&2
|
||||
rm -f "$DMG_OUT"
|
||||
sync
|
||||
sleep $((convert_attempts * 5))
|
||||
done
|
||||
mv -f "$DMG_OUT" "$DMG_PATH"
|
||||
rm -f "$DMG_RW"
|
||||
DMG_RW=""
|
||||
DMG_PATH="$FINAL_DMG"
|
||||
|
||||
echo "[dmg] Notarizing DMG..."
|
||||
DMG_SUBMIT_OUT="$(mktemp /tmp/notarize-dmg-XXXXXX.json)"
|
||||
@@ -143,18 +160,17 @@ fi
|
||||
xcrun stapler staple "$DMG_PATH"
|
||||
echo "[dmg] DMG notarization complete: $DMG_PATH"
|
||||
|
||||
# 4. Final verification
|
||||
# ── Final verification ───────────────────────────────────────────────────────
|
||||
echo "[dmg] Verifying final DMG layout..."
|
||||
VERIFY_MOUNT="$(mktemp -d /tmp/OpenHuman-Verify-XXXXXX)"
|
||||
hdiutil attach "$DMG_PATH" -mountpoint "$VERIFY_MOUNT" -noautoopen
|
||||
|
||||
if [ ! -d "$VERIFY_MOUNT/$APP_NAME" ]; then
|
||||
echo "[dmg] ERROR: .app bundle missing in final DMG"
|
||||
echo "[dmg] ERROR: $APP_NAME missing in final DMG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -L "$VERIFY_MOUNT/Applications" ]; then
|
||||
echo "[dmg] ERROR: Applications symlink missing in final DMG"
|
||||
echo "[dmg] ERROR: Applications symlink missing in final DMG" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
Reference in New Issue
Block a user