#!/bin/zsh set -euo pipefail PROJECT_DIR="${0:A:h:h}" APP_DIR="$PROJECT_DIR/dist/puter.app" ICON_SOURCE="${PUTER_ICON_SOURCE:-$PROJECT_DIR/puter.icon}" STAGING_ROOT="$(mktemp -d /tmp/puter-build.XXXXXX)" STAGED_APP="$STAGING_ROOT/puter.app" CONTENTS_DIR="$STAGED_APP/Contents" SIGN_IDENTITY="${PUTER_SIGN_IDENTITY:--}" APP_VERSION="${PUTER_VERSION:-1.0}" BUILD_NUMBER="${PUTER_BUILD_NUMBER:-1}" trap '/bin/rm -rf -- "$STAGING_ROOT"' EXIT cd "$PROJECT_DIR" swift build -c release --product puter swift build -c release --product puter-helper mkdir -p "$CONTENTS_DIR/MacOS" "$CONTENTS_DIR/Resources" "$CONTENTS_DIR/Frameworks" "$CONTENTS_DIR/Library/LaunchDaemons" cp "$PROJECT_DIR/.build/release/puter" "$CONTENTS_DIR/MacOS/puter" cp "$PROJECT_DIR/.build/release/puter-helper" "$CONTENTS_DIR/Resources/puter-helper" cp "$PROJECT_DIR/Resources/dev.soconnor.puter.helper.plist" "$CONTENTS_DIR/Library/LaunchDaemons/dev.soconnor.puter.helper.plist" cp "$PROJECT_DIR/Resources/Info.plist" "$CONTENTS_DIR/Info.plist" ditto --norsrc "$PROJECT_DIR/.build/release/Sparkle.framework" "$CONTENTS_DIR/Frameworks/Sparkle.framework" chmod +x "$CONTENTS_DIR/MacOS/puter" "$CONTENTS_DIR/Resources/puter-helper" if ! otool -l "$CONTENTS_DIR/MacOS/puter" | grep -q '@executable_path/../Frameworks'; then install_name_tool -add_rpath '@executable_path/../Frameworks' "$CONTENTS_DIR/MacOS/puter" fi plutil -lint "$CONTENTS_DIR/Info.plist" "$CONTENTS_DIR/Library/LaunchDaemons/dev.soconnor.puter.helper.plist" >/dev/null /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $APP_VERSION" "$CONTENTS_DIR/Info.plist" /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $BUILD_NUMBER" "$CONTENTS_DIR/Info.plist" if [[ -n "${PUTER_UPDATE_FEED_URL:-}" && -n "${PUTER_UPDATE_PUBLIC_KEY:-}" ]]; then /usr/libexec/PlistBuddy -c "Add :SUFeedURL string $PUTER_UPDATE_FEED_URL" "$CONTENTS_DIR/Info.plist" /usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string $PUTER_UPDATE_PUBLIC_KEY" "$CONTENTS_DIR/Info.plist" /usr/libexec/PlistBuddy -c "Add :SUEnableAutomaticChecks bool true" "$CONTENTS_DIR/Info.plist" /usr/libexec/PlistBuddy -c "Add :SUAllowsAutomaticUpdates bool true" "$CONTENTS_DIR/Info.plist" fi SMC_SOURCE="${PUTER_SMC_SOURCE:-$PROJECT_DIR/Resources/smc}" if [[ ! -x "$SMC_SOURCE" ]]; then SMC_SOURCE="/Applications/Stats.app/Contents/Resources/smc" fi if [[ -x "$SMC_SOURCE" ]]; then ditto --norsrc "$SMC_SOURCE" "$CONTENTS_DIR/Resources/smc" chmod +x "$CONTENTS_DIR/Resources/smc" cp "$PROJECT_DIR/Resources/Stats-SMC-LICENSE.txt" "$CONTENTS_DIR/Resources/Stats-SMC-LICENSE.txt" elif [[ "${PUTER_RELEASE_BUILD:-0}" == "1" ]]; then print -u2 "A release build requires PUTER_SMC_SOURCE or Resources/smc." exit 2 fi if [[ -d "$ICON_SOURCE" ]]; then print "Using Icon Composer source: $ICON_SOURCE" xcrun actool "$ICON_SOURCE" \ --compile "$CONTENTS_DIR/Resources" \ --platform macosx \ --minimum-deployment-target 14.0 \ --app-icon puter \ --output-partial-info-plist "$CONTENTS_DIR/Resources/IconInfo.plist" >/dev/null [[ -s "$CONTENTS_DIR/Resources/Assets.car" ]] || { print -u2 "Icon Composer did not produce an Assets.car file" exit 1 } [[ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconName' "$CONTENTS_DIR/Info.plist")" == "puter" ]] || { print -u2 "CFBundleIconName must reference the Icon Composer asset" exit 1 } if /usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$CONTENTS_DIR/Info.plist" >/dev/null 2>&1; then print -u2 "CFBundleIconFile would override the live Icon Composer asset" exit 1 fi fi # Finder and cloud-sync metadata can make an otherwise valid app bundle # unsignable when rebuilding in place. xattr -cr "$STAGED_APP" SIGN_ARGS=(--force --options runtime --sign "$SIGN_IDENTITY") if [[ "$SIGN_IDENTITY" != "-" ]]; then SIGN_ARGS+=(--timestamp) fi # Sign every executable from the inside out. Avoid --deep, which can mask an # incorrectly signed nested helper and produces fragile release bundles. if [[ -x "$CONTENTS_DIR/Resources/smc" ]]; then codesign "${SIGN_ARGS[@]}" --identifier dev.soconnor.puter.smc "$CONTENTS_DIR/Resources/smc" >/dev/null fi SPARKLE_CURRENT="$CONTENTS_DIR/Frameworks/Sparkle.framework/Versions/Current" codesign "${SIGN_ARGS[@]}" "$SPARKLE_CURRENT/Autoupdate" >/dev/null codesign "${SIGN_ARGS[@]}" "$SPARKLE_CURRENT/XPCServices/Downloader.xpc" >/dev/null codesign "${SIGN_ARGS[@]}" "$SPARKLE_CURRENT/XPCServices/Installer.xpc" >/dev/null codesign "${SIGN_ARGS[@]}" "$SPARKLE_CURRENT/Updater.app" >/dev/null codesign "${SIGN_ARGS[@]}" "$CONTENTS_DIR/Frameworks/Sparkle.framework" >/dev/null codesign "${SIGN_ARGS[@]}" --identifier dev.soconnor.puter.helper "$CONTENTS_DIR/Resources/puter-helper" >/dev/null APP_ENTITLEMENTS="$PROJECT_DIR/Resources/puter.entitlements" if [[ "$SIGN_IDENTITY" == "-" ]]; then APP_ENTITLEMENTS="$PROJECT_DIR/Resources/puter-adhoc.entitlements" fi codesign "${SIGN_ARGS[@]}" --entitlements "$APP_ENTITLEMENTS" "$STAGED_APP" >/dev/null codesign --verify --deep --strict "$STAGED_APP" if [[ "${PUTER_RELEASE_BUILD:-0}" == "1" ]]; then [[ "$SIGN_IDENTITY" != "-" ]] || { print -u2 "Release builds cannot use an ad-hoc signature."; exit 2; } codesign -dvv "$STAGED_APP" 2>&1 | grep -q '^Authority=Developer ID Application:' || { print -u2 "Release build is not signed with a Developer ID Application certificate." exit 2 } [[ -n "${PUTER_UPDATE_FEED_URL:-}" && -n "${PUTER_UPDATE_PUBLIC_KEY:-}" ]] || { print -u2 "Release builds require PUTER_UPDATE_FEED_URL and PUTER_UPDATE_PUBLIC_KEY." exit 2 } fi mkdir -p "$PROJECT_DIR/dist" /bin/rm -rf -- "$APP_DIR" ditto --norsrc "$STAGED_APP" "$APP_DIR" # File-provider workspaces can attach Finder/provenance metadata during the # final copy. Clean and sign at the delivery path so verification reflects # the bundle users actually launch. xattr -cr "$APP_DIR" codesign --verify --deep --strict "$APP_DIR" print "Built $APP_DIR"