104 lines
4.6 KiB
Bash
Executable File
104 lines
4.6 KiB
Bash
Executable File
#!/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
|