new release script
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
cd /Volumes/Developer/Applications/DbSyn
|
||||
&& rm -rf ./build
|
||||
&&
|
||||
xcodebuild
|
||||
-project
|
||||
DBSyn.xcodeproj
|
||||
-scheme
|
||||
DBSyn
|
||||
-configuration
|
||||
Debug
|
||||
-destination
|
||||
'platform=macOS,arch=arm64'
|
||||
-derivedDataPath
|
||||
./build
|
||||
clean
|
||||
build
|
||||
&&
|
||||
{
|
||||
pkill
|
||||
-x
|
||||
DBSyn
|
||||
2>/dev/null
|
||||
||
|
||||
true;
|
||||
open
|
||||
./build/Build/Products/Debug/DBSyn.app;
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# DBSyn — Developer ID direct distribution (signed and notarized app + DMG)
|
||||
# Install at: <project>/scripts/release-dbsyn-direct.sh
|
||||
# Run from any directory: ./scripts/release-dbsyn-direct.sh
|
||||
|
||||
APP_NAME="DBSyn"
|
||||
SCHEME="DBSyn"
|
||||
PROJECT="DBSyn.xcodeproj"
|
||||
TEAM_ID="${TEAM_ID:-39R6U3437D}"
|
||||
SIGNING_IDENTITY="${SIGNING_IDENTITY:-B99D353C88996866C49E4389B375645F76870A3F}"
|
||||
NOTARY_PROFILE="${NOTARY_PROFILE:-orion-notary}"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ -d "$SCRIPT_DIR/$PROJECT" ]]; then
|
||||
ROOT_DIR="$SCRIPT_DIR"
|
||||
elif [[ -d "$SCRIPT_DIR/../$PROJECT" ]]; then
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
else
|
||||
echo "ERROR: $PROJECT not found next to this script or in its parent directory." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
BUILD_DIR="$ROOT_DIR/build/direct-distribution"
|
||||
ARCHIVE_PATH="$BUILD_DIR/$APP_NAME.xcarchive"
|
||||
EXPORT_DIR="$BUILD_DIR/export"
|
||||
DMG_STAGE="$BUILD_DIR/dmg-stage"
|
||||
EXPORT_OPTIONS="$BUILD_DIR/ExportOptions.plist"
|
||||
APP_ZIP="$BUILD_DIR/$APP_NAME-notarization.zip"
|
||||
RELEASE_DIR="$ROOT_DIR/releases"
|
||||
DMG_PATH="$RELEASE_DIR/$APP_NAME.dmg"
|
||||
|
||||
for tool in xcodebuild xcrun codesign spctl ditto hdiutil shasum security; do
|
||||
command -v "$tool" >/dev/null || { echo "ERROR: Missing $tool" >&2; exit 1; }
|
||||
done
|
||||
[[ -d "$ROOT_DIR/$PROJECT" ]] || { echo "ERROR: Missing $PROJECT" >&2; exit 1; }
|
||||
security find-identity -v -p codesigning | grep -Fq "$SIGNING_IDENTITY" || {
|
||||
echo "ERROR: Signing identity $SIGNING_IDENTITY not available in Keychain." >&2
|
||||
exit 1
|
||||
}
|
||||
# Check that notarytool profile exists before spending time compiling.
|
||||
xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" >/dev/null || {
|
||||
echo "ERROR: Notarytool profile '$NOTARY_PROFILE' is unavailable or invalid." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
printf '\n========== DBSyn DIRECT DISTRIBUTION ==========\n'
|
||||
cd "$ROOT_DIR"
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR" "$EXPORT_DIR" "$DMG_STAGE" "$RELEASE_DIR"
|
||||
# Keep previous releases until a new notarized DMG has been successfully built.
|
||||
|
||||
cat > "$EXPORT_OPTIONS" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>method</key><string>developer-id</string>
|
||||
<key>signingStyle</key><string>automatic</string>
|
||||
<key>teamID</key><string>$TEAM_ID</string>
|
||||
</dict></plist>
|
||||
PLIST
|
||||
|
||||
echo '==> Archiving DBSyn (Release)...'
|
||||
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \
|
||||
-destination 'generic/platform=macOS' -archivePath "$ARCHIVE_PATH" \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID" CODE_SIGN_STYLE=Automatic \
|
||||
clean archive
|
||||
|
||||
echo '==> Exporting Developer ID application...'
|
||||
xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" \
|
||||
-exportPath "$EXPORT_DIR" -exportOptionsPlist "$EXPORT_OPTIONS"
|
||||
APP_PATH="$EXPORT_DIR/$APP_NAME.app"
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Enforce Developer ID signing with hardened runtime
|
||||
# ------------------------------------------------------------
|
||||
|
||||
echo
|
||||
echo "==> Signing DBSyn with hardened runtime..."
|
||||
|
||||
codesign \
|
||||
--force \
|
||||
--deep \
|
||||
--options runtime \
|
||||
--timestamp \
|
||||
--sign "$SIGNING_IDENTITY" \
|
||||
"$APP_PATH"
|
||||
|
||||
echo "==> Verifying hardened runtime..."
|
||||
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
|
||||
SIGNATURE_INFO="$(codesign -dv --verbose=4 "$APP_PATH" 2>&1)"
|
||||
|
||||
printf '%s\n' "$SIGNATURE_INFO"
|
||||
|
||||
if [[ "$SIGNATURE_INFO" != *"(runtime)"* ]]; then
|
||||
echo "ERROR: Hardened runtime signing failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Hardened runtime verified."
|
||||
|
||||
|
||||
[[ -d "$APP_PATH" ]] || { echo "ERROR: Exported app missing: $APP_PATH" >&2; exit 1; }
|
||||
|
||||
echo '==> Checking application signature and hardened runtime...'
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
codesign -dv --verbose=4 "$APP_PATH" 2>&1 | grep -E 'Identifier=|Authority=|TeamIdentifier=|Runtime Version=|flags=' || true
|
||||
# codesign -d prints details to stderr and may return a nonzero status even
|
||||
# when the signature is valid. Under `set -o pipefail`, piping it to grep
|
||||
# can therefore reject a correctly signed app. Capture once and inspect.
|
||||
SIGNATURE_INFO="$(codesign -dv --verbose=4 "$APP_PATH" 2>&1 || true)"
|
||||
ACTUAL_TEAM="$(printf '%s\n' "$SIGNATURE_INFO" | sed -n 's/^TeamIdentifier=//p' | head -n 1)"
|
||||
[[ "$ACTUAL_TEAM" == "$TEAM_ID" ]] || { echo "ERROR: Unexpected signing team: $ACTUAL_TEAM" >&2; exit 1; }
|
||||
if ! grep -Eq 'flags=0x[[:xdigit:]]+\(runtime\)' <<< "$SIGNATURE_INFO"; then
|
||||
echo 'ERROR: App is not signed with the hardened runtime required for notarization.' >&2
|
||||
exit 1
|
||||
fi
|
||||
echo '==> Hardened runtime verified.'
|
||||
|
||||
echo '==> Notarizing application...'
|
||||
ditto -c -k --keepParent "$APP_PATH" "$APP_ZIP"
|
||||
xcrun notarytool submit "$APP_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun stapler staple "$APP_PATH"
|
||||
xcrun stapler validate "$APP_PATH"
|
||||
spctl --assess --type execute --verbose=4 "$APP_PATH"
|
||||
|
||||
echo '==> Creating installer DMG...'
|
||||
ditto "$APP_PATH" "$DMG_STAGE/$APP_NAME.app"
|
||||
ln -s /Applications "$DMG_STAGE/Applications"
|
||||
TMP_DMG="$BUILD_DIR/$APP_NAME.dmg"
|
||||
hdiutil create -volname "$APP_NAME" -srcfolder "$DMG_STAGE" -ov -format UDZO "$TMP_DMG"
|
||||
|
||||
echo '==> Signing and notarizing DMG...'
|
||||
codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$TMP_DMG"
|
||||
codesign --verify --verbose=2 "$TMP_DMG"
|
||||
xcrun notarytool submit "$TMP_DMG" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun stapler staple "$TMP_DMG"
|
||||
xcrun stapler validate "$TMP_DMG"
|
||||
spctl --assess --type open --context context:primary-signature --verbose=4 "$TMP_DMG"
|
||||
|
||||
# Publish only after all checks have passed.
|
||||
cp -f "$TMP_DMG" "$DMG_PATH"
|
||||
echo '==> Release ready:'
|
||||
ls -lh "$DMG_PATH"
|
||||
shasum -a 256 "$DMG_PATH"
|
||||
printf '\n========== DBSyn RELEASE COMPLETE ==========\n%s\n' "$DMG_PATH"
|
||||
Executable
+157
@@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# DBSyn direct distribution: Developer ID signed, notarized app and DMG.
|
||||
# macOS/Xcode required. Install as scripts/release-dbsyn-direct.sh.
|
||||
APP_NAME=DBSyn
|
||||
SCHEME=DBSyn
|
||||
PROJECT=DBSyn.xcodeproj
|
||||
TEAM_ID="${TEAM_ID:-39R6U3437D}"
|
||||
SIGNING_IDENTITY="${SIGNING_IDENTITY:-B99D353C88996866C49E4389B375645F76870A3F}"
|
||||
NOTARY_PROFILE="${NOTARY_PROFILE:-orion-notary}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [[ -d "$SCRIPT_DIR/$PROJECT" ]]; then ROOT_DIR="$SCRIPT_DIR"
|
||||
elif [[ -d "$SCRIPT_DIR/../$PROJECT" ]]; then ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
else echo "ERROR: Cannot locate $PROJECT" >&2; exit 1; fi
|
||||
BUILD_DIR="$ROOT_DIR/build/direct-distribution"
|
||||
ARCHIVE_PATH="$BUILD_DIR/$APP_NAME.xcarchive"
|
||||
EXPORT_DIR="$BUILD_DIR/export"
|
||||
EXPORT_OPTIONS="$BUILD_DIR/ExportOptions.plist"
|
||||
APP_PATH="$EXPORT_DIR/$APP_NAME.app"
|
||||
APP_ZIP="$BUILD_DIR/$APP_NAME-notarization.zip"
|
||||
DMG_STAGE="$BUILD_DIR/dmg-stage"
|
||||
RW_DMG="$BUILD_DIR/$APP_NAME-rw.dmg"
|
||||
TMP_DMG="$BUILD_DIR/$APP_NAME.dmg"
|
||||
RELEASE_DIR="$ROOT_DIR/releases"
|
||||
DMG_PATH="$RELEASE_DIR/$APP_NAME.dmg"
|
||||
MOUNT_POINT="$BUILD_DIR/dmg-mount"
|
||||
|
||||
for tool in xcodebuild xcrun codesign spctl ditto hdiutil shasum security swift sips /usr/libexec/PlistBuddy; do
|
||||
command -v "$tool" >/dev/null || { echo "ERROR: Missing $tool" >&2; exit 1; }
|
||||
done
|
||||
if ! security find-identity -v -p codesigning | grep -F "$SIGNING_IDENTITY" >/dev/null; then
|
||||
echo "ERROR: Developer ID identity not found: $SIGNING_IDENTITY" >&2; exit 1
|
||||
fi
|
||||
xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" >/dev/null || {
|
||||
echo "ERROR: Notarytool profile '$NOTARY_PROFILE' unavailable" >&2; exit 1;
|
||||
}
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR" "$EXPORT_DIR" "$DMG_STAGE" "$RELEASE_DIR" "$MOUNT_POINT"
|
||||
cat > "$EXPORT_OPTIONS" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0"><dict>
|
||||
<key>method</key><string>developer-id</string>
|
||||
<key>signingStyle</key><string>automatic</string>
|
||||
<key>teamID</key><string>$TEAM_ID</string>
|
||||
</dict></plist>
|
||||
PLIST
|
||||
|
||||
echo '==> Archiving DBSyn with hardened runtime...'
|
||||
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \
|
||||
-destination 'generic/platform=macOS' -archivePath "$ARCHIVE_PATH" \
|
||||
DEVELOPMENT_TEAM="$TEAM_ID" CODE_SIGN_STYLE=Automatic \
|
||||
ENABLE_HARDENED_RUNTIME=YES \
|
||||
clean archive
|
||||
|
||||
echo '==> Exporting Developer ID app...'
|
||||
xcodebuild -exportArchive -archivePath "$ARCHIVE_PATH" \
|
||||
-exportPath "$EXPORT_DIR" -exportOptionsPlist "$EXPORT_OPTIONS"
|
||||
[[ -d "$APP_PATH" ]] || { echo "ERROR: Missing exported app" >&2; exit 1; }
|
||||
|
||||
# Xcode's generated Info.plist can omit CFBundleIconFile even when the
|
||||
# build setting specifies it. Set it on the exported bundle BEFORE the
|
||||
# final Developer ID signature, so the signed app includes the icon key.
|
||||
ICON_PATH="$APP_PATH/Contents/Resources/DBSyn.icns"
|
||||
[[ -s "$ICON_PATH" ]] || { echo "ERROR: Missing bundled icon $ICON_PATH" >&2; exit 1; }
|
||||
PLIST_PATH="$APP_PATH/Contents/Info.plist"
|
||||
ICON_KEY="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$PLIST_PATH" 2>/dev/null || true)"
|
||||
if [[ "$ICON_KEY" != DBSyn && "$ICON_KEY" != DBSyn.icns ]]; then
|
||||
echo "==> Setting CFBundleIconFile in exported app (before signing)..."
|
||||
if /usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$PLIST_PATH" >/dev/null 2>&1; then
|
||||
/usr/libexec/PlistBuddy -c 'Set :CFBundleIconFile DBSyn.icns' "$PLIST_PATH"
|
||||
else
|
||||
/usr/libexec/PlistBuddy -c 'Add :CFBundleIconFile string DBSyn.icns' "$PLIST_PATH"
|
||||
fi
|
||||
fi
|
||||
ICON_KEY="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' "$PLIST_PATH")"
|
||||
[[ "$ICON_KEY" == DBSyn || "$ICON_KEY" == DBSyn.icns ]] || {
|
||||
echo "ERROR: CFBundleIconFile still incorrect: '$ICON_KEY'" >&2; exit 1;
|
||||
}
|
||||
echo "==> Application icon verified: $ICON_KEY"
|
||||
sips -g pixelWidth -g pixelHeight "$ICON_PATH"
|
||||
|
||||
# Export may omit hardened runtime; sign exported app explicitly.
|
||||
# Sign embedded code first if your project adds frameworks/helpers in future.
|
||||
echo '==> Developer ID signing with hardened runtime...'
|
||||
codesign --force --deep --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$APP_PATH"
|
||||
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
|
||||
SIGNATURE_INFO="$(codesign -dv --verbose=4 "$APP_PATH" 2>&1 || true)"
|
||||
printf '%s\n' "$SIGNATURE_INFO" | grep -E 'Identifier=|Authority=|TeamIdentifier=|flags=' || true
|
||||
[[ "$SIGNATURE_INFO" == *"TeamIdentifier=$TEAM_ID"* ]] || { echo 'ERROR: Wrong signing team' >&2; exit 1; }
|
||||
[[ "$SIGNATURE_INFO" == *'(runtime)'* ]] || { echo 'ERROR: Hardened runtime missing' >&2; exit 1; }
|
||||
|
||||
echo '==> Notarizing and stapling app...'
|
||||
ditto -c -k --keepParent "$APP_PATH" "$APP_ZIP"
|
||||
xcrun notarytool submit "$APP_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun stapler staple "$APP_PATH"
|
||||
xcrun stapler validate "$APP_PATH"
|
||||
spctl --assess --type execute --verbose=4 "$APP_PATH"
|
||||
|
||||
echo '==> Preparing writable DMG...'
|
||||
ditto "$APP_PATH" "$DMG_STAGE/$APP_NAME.app"
|
||||
ln -s /Applications "$DMG_STAGE/Applications"
|
||||
# Create writable image first: the custom volume icon must be set ON the
|
||||
# mounted filesystem, not on the staging directory.
|
||||
hdiutil create -size 100m -fs HFS+ -volname "$APP_NAME" -format UDRW -ov "$RW_DMG"
|
||||
MOUNTED=0
|
||||
cleanup() {
|
||||
if [[ "$MOUNTED" == 1 ]]; then hdiutil detach "$MOUNT_POINT" -quiet || true; fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
hdiutil attach "$RW_DMG" -nobrowse -noverify -mountpoint "$MOUNT_POINT" -quiet
|
||||
MOUNTED=1
|
||||
ditto "$DMG_STAGE/$APP_NAME.app" "$MOUNT_POINT/$APP_NAME.app"
|
||||
ln -s /Applications "$MOUNT_POINT/Applications"
|
||||
cp "$ICON_PATH" "$MOUNT_POINT/.VolumeIcon.icns"
|
||||
xcrun SetFile -a C "$MOUNT_POINT"
|
||||
hdiutil detach "$MOUNT_POINT" -quiet
|
||||
MOUNTED=0
|
||||
hdiutil convert "$RW_DMG" -format UDZO -o "$TMP_DMG" -ov
|
||||
|
||||
# A DMG *file* icon is different from its mounted volume icon. Use AppKit to
|
||||
# assign a Finder custom icon to the DMG file before signing/notarization.
|
||||
# This creates Finder metadata (custom icon resource/finder flag).
|
||||
ICON_SWIFT="$BUILD_DIR/set-dmg-file-icon.swift"
|
||||
cat > "$ICON_SWIFT" <<'SWIFT'
|
||||
import AppKit
|
||||
import Foundation
|
||||
let arguments = CommandLine.arguments
|
||||
precondition(arguments.count == 3, "Usage: icon.swift <icon.icns> <file.dmg>")
|
||||
guard let icon = NSImage(contentsOfFile: arguments[1]) else {
|
||||
fputs("ERROR: Cannot load ICNS image\n", stderr); exit(1)
|
||||
}
|
||||
guard NSWorkspace.shared.setIcon(icon, forFile: arguments[2], options: []) else {
|
||||
fputs("ERROR: NSWorkspace could not set DMG Finder icon\n", stderr); exit(1)
|
||||
}
|
||||
print("Custom Finder icon assigned to DMG file")
|
||||
SWIFT
|
||||
echo '==> Assigning Finder icon to DMG file...'
|
||||
swift "$ICON_SWIFT" "$ICON_PATH" "$TMP_DMG"
|
||||
|
||||
echo '==> Signing and notarizing DMG...'
|
||||
codesign --force --timestamp --sign "$SIGNING_IDENTITY" "$TMP_DMG"
|
||||
codesign --verify --verbose=2 "$TMP_DMG"
|
||||
xcrun notarytool submit "$TMP_DMG" --keychain-profile "$NOTARY_PROFILE" --wait
|
||||
xcrun stapler staple "$TMP_DMG"
|
||||
xcrun stapler validate "$TMP_DMG"
|
||||
spctl --assess --type open --context context:primary-signature --verbose=4 "$TMP_DMG"
|
||||
|
||||
# Publish using ditto so Finder metadata/resource forks survive the copy.
|
||||
ditto "$TMP_DMG" "$DMG_PATH"
|
||||
echo '==> Release ready:'
|
||||
ls -lh "$DMG_PATH"
|
||||
shasum -a 256 "$DMG_PATH"
|
||||
printf '\n========== DBSyn RELEASE COMPLETE ==========\n%s\n' "$DMG_PATH"
|
||||
Reference in New Issue
Block a user