#!/bin/zsh set -euo pipefail PROJECT_DIR="${0:A:h:h}" APP_PATH="${1:-$PROJECT_DIR/dist/puter.app}" DMG_PATH="${2:-$PROJECT_DIR/dist/puter-macOS.dmg}" RELEASE_VALIDATION="${PUTER_RELEASE_BUILD:-0}" MOUNT_DIR="" MOUNT_DEVICE="" cleanup() { if [[ -n "$MOUNT_DEVICE" ]]; then hdiutil detach "$MOUNT_DEVICE" >/dev/null 2>&1 || true fi if [[ -n "$MOUNT_DIR" && -d "$MOUNT_DIR" ]]; then rmdir "$MOUNT_DIR" >/dev/null 2>&1 || true fi } trap cleanup EXIT fail() { print -u2 "Package validation failed: $1" exit 2 } validate_app() { local app="$1" local contents="$app/Contents" local info="$contents/Info.plist" [[ -d "$app" ]] || fail "app bundle not found at $app" [[ -x "$contents/MacOS/puter" ]] || fail "main executable is missing" [[ -x "$contents/Resources/puter-helper" ]] || fail "privileged helper is missing" if [[ "$RELEASE_VALIDATION" == "1" ]]; then [[ -x "$contents/Resources/smc" ]] || fail "release SMC backend is missing" [[ -f "$contents/Resources/Stats-SMC-LICENSE.txt" ]] || fail "release SMC license is missing" elif [[ -e "$contents/Resources/smc" ]]; then [[ -x "$contents/Resources/smc" ]] || fail "bundled SMC backend is not executable" [[ -f "$contents/Resources/Stats-SMC-LICENSE.txt" ]] || fail "bundled SMC license is missing" fi [[ -f "$contents/Library/LaunchDaemons/dev.soconnor.puter.helper.plist" ]] || fail "helper launch daemon plist is missing" [[ -d "$contents/Frameworks/Sparkle.framework" ]] || fail "Sparkle framework is missing" [[ -f "$contents/Resources/Assets.car" ]] || fail "Icon Composer asset catalog is missing" plutil -lint "$info" "$contents/Library/LaunchDaemons/dev.soconnor.puter.helper.plist" >/dev/null [[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$info")" == "dev.soconnor.puter" ]] \ || fail "unexpected bundle identifier" [[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconName' "$info")" == "puter" ]] \ || fail "Icon Composer asset is not configured" [[ "$(/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' "$info")" == "14.0" ]] \ || fail "unexpected deployment target" if /usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$info" >/dev/null 2>&1; then fail "legacy CFBundleIconFile overrides the Icon Composer asset" fi [[ "$(otool -l "$contents/MacOS/puter")" == *'@executable_path/../Frameworks'* ]] \ || fail "framework runtime search path is missing" [[ "$(otool -L "$contents/MacOS/puter")" == *'Sparkle.framework'* ]] \ || fail "main executable is not linked to Sparkle" codesign --verify --deep --strict "$app" local signature signature="$(codesign -dvv "$app" 2>&1)" [[ "$signature" == *'runtime'* ]] || fail "Hardened Runtime is missing" if [[ "$RELEASE_VALIDATION" == "1" ]]; then [[ "$signature" == *'Authority=Developer ID Application:'* ]] \ || fail "release app is not Developer ID Application signed" [[ -n "$(print -r -- "$signature" | sed -n 's/^TeamIdentifier=//p')" ]] \ || fail "release app has no Team Identifier" /usr/libexec/PlistBuddy -c 'Print :SUFeedURL' "$info" >/dev/null 2>&1 \ || fail "release app has no Sparkle feed" /usr/libexec/PlistBuddy -c 'Print :SUPublicEDKey' "$info" >/dev/null 2>&1 \ || fail "release app has no Sparkle public key" local entitlements entitlements="$(codesign -d --entitlements - "$app" 2>/dev/null || true)" [[ "$entitlements" != *'com.apple.security.get-task-allow'* ]] \ || fail "release app contains get-task-allow" [[ "$entitlements" != *'com.apple.security.cs.disable-library-validation'* ]] \ || fail "release app disables library validation" fi } validate_app "$APP_PATH" if [[ -f "$DMG_PATH" ]]; then hdiutil verify "$DMG_PATH" >/dev/null if [[ "$RELEASE_VALIDATION" == "1" ]]; then codesign --verify --strict "$DMG_PATH" fi MOUNT_DIR="$(mktemp -d /tmp/puter-package.XXXXXX)" MOUNT_DEVICE="$(hdiutil attach -readonly -nobrowse -mountpoint "$MOUNT_DIR" "$DMG_PATH" | awk '/Apple_APFS/ {print $1; exit}')" [[ -n "$MOUNT_DEVICE" ]] || fail "DMG did not mount" [[ -L "$MOUNT_DIR/Applications" ]] || fail "DMG Applications shortcut is missing" [[ "$(readlink "$MOUNT_DIR/Applications")" == "/Applications" ]] \ || fail "DMG Applications shortcut has the wrong target" validate_app "$MOUNT_DIR/puter.app" fi if [[ -f "$DMG_PATH" ]]; then print "Validated $APP_PATH and $DMG_PATH" else print "Validated $APP_PATH" fi