Add advanced monitoring and release infrastructure
Build and test / macos (push) Canceled after 0s
Signed release / release (push) Canceled after 0s

This commit is contained in:
2026-08-15 18:23:35 -04:00
parent a67bb5fdf3
commit 0da94715be
38 changed files with 4219 additions and 406 deletions
+63 -6
View File
@@ -7,22 +7,48 @@ 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
swift build -c release --product puter
swift build -c release --product puter-helper
mkdir -p "$CONTENTS_DIR/MacOS" "$CONTENTS_DIR/Resources"
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"
chmod +x "$CONTENTS_DIR/MacOS/puter"
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"
SMC_SOURCE="/Applications/Stats.app/Contents/Resources/smc"
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
@@ -51,8 +77,40 @@ fi
# Finder and cloud-sync metadata can make an otherwise valid app bundle
# unsignable when rebuilding in place.
xattr -cr "$STAGED_APP"
codesign --force --deep --sign - "$STAGED_APP" >/dev/null
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"
@@ -61,6 +119,5 @@ ditto --norsrc "$STAGED_APP" "$APP_DIR"
# final copy. Clean and sign at the delivery path so verification reflects
# the bundle users actually launch.
xattr -cr "$APP_DIR"
codesign --force --deep --sign - "$APP_DIR" >/dev/null
codesign --verify --deep --strict "$APP_DIR"
print "Built $APP_DIR"
+6
View File
@@ -4,6 +4,7 @@ set -euo pipefail
PROJECT_DIR="${0:A:h:h}"
APP_PATH="$PROJECT_DIR/dist/puter.app"
DMG_PATH="$PROJECT_DIR/dist/puter-macOS.dmg"
SIGN_IDENTITY="${PUTER_SIGN_IDENTITY:--}"
STAGING_DIR="$(mktemp -d /tmp/puter-dmg.XXXXXX)"
cleanup() {
@@ -28,4 +29,9 @@ hdiutil create \
-ov \
"$DMG_PATH"
if [[ "$SIGN_IDENTITY" != "-" ]]; then
codesign --force --timestamp --sign "$SIGN_IDENTITY" "$DMG_PATH"
codesign --verify --strict "$DMG_PATH"
fi
print "Built $DMG_PATH"
+22
View File
@@ -0,0 +1,22 @@
#!/bin/zsh
set -euo pipefail
PROJECT_DIR="${0:A:h:h}"
ARCHIVES_DIR="${1:-$PROJECT_DIR/dist/updates}"
DOWNLOAD_PREFIX="${PUTER_UPDATE_DOWNLOAD_PREFIX:-}"
PRIVATE_KEY="${PUTER_SPARKLE_PRIVATE_KEY:-}"
GENERATOR="$PROJECT_DIR/.build/artifacts/sparkle/Sparkle/bin/generate_appcast"
if [[ -z "$DOWNLOAD_PREFIX" || -z "$PRIVATE_KEY" ]]; then
print -u2 "Set PUTER_UPDATE_DOWNLOAD_PREFIX and PUTER_SPARKLE_PRIVATE_KEY."
exit 2
fi
if [[ ! -x "$GENERATOR" ]]; then
swift package resolve --package-path "$PROJECT_DIR"
fi
mkdir -p "$ARCHIVES_DIR"
print -rn -- "$PRIVATE_KEY" | "$GENERATOR" \
--ed-key-file - \
--download-url-prefix "$DOWNLOAD_PREFIX" \
"$ARCHIVES_DIR"
print "Generated $ARCHIVES_DIR/appcast.xml"
+35
View File
@@ -0,0 +1,35 @@
#!/bin/zsh
set -euo pipefail
PROJECT_DIR="${0:A:h:h}"
DMG_PATH="${1:-$PROJECT_DIR/dist/puter-macOS.dmg}"
APP_PATH="$PROJECT_DIR/dist/puter.app"
NOTARY_PROFILE="${PUTER_NOTARY_PROFILE:-}"
NOTARY_KEY_PATH="${PUTER_NOTARY_KEY_PATH:-}"
NOTARY_KEY_ID="${PUTER_NOTARY_KEY_ID:-}"
NOTARY_ISSUER="${PUTER_NOTARY_ISSUER:-}"
if [[ -z "$NOTARY_PROFILE" && ( -z "$NOTARY_KEY_PATH" || -z "$NOTARY_KEY_ID" || -z "$NOTARY_ISSUER" ) ]]; then
print -u2 "Set PUTER_NOTARY_PROFILE or the PUTER_NOTARY_KEY_PATH, PUTER_NOTARY_KEY_ID, and PUTER_NOTARY_ISSUER variables."
exit 2
fi
if [[ ! -f "$DMG_PATH" ]]; then
print -u2 "DMG not found: $DMG_PATH"
exit 2
fi
codesign --verify --strict "$DMG_PATH"
if [[ -n "$NOTARY_PROFILE" ]]; then
xcrun notarytool submit "$DMG_PATH" --keychain-profile "$NOTARY_PROFILE" --wait
else
xcrun notarytool submit "$DMG_PATH" \
--key "$NOTARY_KEY_PATH" --key-id "$NOTARY_KEY_ID" --issuer "$NOTARY_ISSUER" --wait
fi
xcrun stapler staple "$DMG_PATH"
xcrun stapler validate "$DMG_PATH"
if [[ -d "$APP_PATH" ]]; then
xcrun stapler staple "$APP_PATH"
xcrun stapler validate "$APP_PATH"
fi
spctl --assess --type open --context context:primary-signature -v "$DMG_PATH"
print "Notarized and stapled $DMG_PATH"
+103
View File
@@ -0,0 +1,103 @@
#!/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