diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..9a897b6 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/RsyncStudio.iml b/.idea/RsyncStudio.iml new file mode 100644 index 0000000..6102194 --- /dev/null +++ b/.idea/RsyncStudio.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f31f777 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..4603f60 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index c6741ac..e3b153a 100755 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Background automation remains active while the application is running, including This project targets Developer ID signed/notarised direct distribution, not the Mac App Store. Current hardening includes Hardened Runtime, validated rsync executables, protected advanced rsync arguments, non-interactive SSH with timeouts/keepalives, normal SSH host-key verification, private-key/agent authentication only, unattended `--delete` authorisation, local source/destination overlap protection, sanitised/capped run logs, configurable history retention, a privacy manifest, and persistent-store recovery mode. -See `Docs/RELEASE.md` and run `Scripts/security-check.sh` before producing a release archive. +See `Docs/RELEASE.md` and run `Scripts/old/security-check.sh` before producing a release archive. ## Professional reliability features @@ -54,4 +54,4 @@ The production build includes job preflight health checks, destination-volume id ## Commercial direct-distribution build -On the release Mac, `Scripts/release.sh` creates the Developer ID signed, notarized and stapled DMG plus its SHA-256 checksum. The script never publishes automatically. See `Docs/COMMERCIAL_RELEASE_STAGE2.md`. +On the release Mac, `Scripts/old/release.sh` creates the Developer ID signed, notarized and stapled DMG plus its SHA-256 checksum. The script never publishes automatically. See `Docs/COMMERCIAL_RELEASE_STAGE2.md`. diff --git a/Rsync Studio/Resources/Rsync Studio Help.md b/Rsync Studio/Resources/Rsync Studio Help.md index 91baef3..ac794df 100755 --- a/Rsync Studio/Resources/Rsync Studio Help.md +++ b/Rsync Studio/Resources/Rsync Studio Help.md @@ -548,7 +548,7 @@ The bundled privacy manifest declares no tracking and no collected data. ## Release verification -The project contains `Docs/RELEASE.md` and `Scripts/security-check.sh` with Developer ID, notarisation, signature and Gatekeeper verification steps for production builds. +The project contains `Docs/RELEASE.md` and `../../Scripts/old/security-check.sh` with Developer ID, notarisation, signature and Gatekeeper verification steps for production builds. # Production Reliability diff --git a/Scripts/build.sh b/Scripts/build.sh new file mode 100644 index 0000000..f4c4756 --- /dev/null +++ b/Scripts/build.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd /Volumes/Developer/Applications/DbSyn + +# Remove previous build +rm -rf ./build + +# Clean and build DBSyn +xcodebuild \ + -project DBSyn.xcodeproj \ + -scheme DBSyn \ + -configuration Debug \ + -destination 'platform=macOS,arch=arm64' \ + -derivedDataPath ./build \ + clean build + +# Stop any running instance +pkill -x DBSyn 2>/dev/null || true + +# Launch the newly built application +open ./build/Build/Products/Debug/DBSyn.app \ No newline at end of file diff --git a/Scripts/license-keygen.swift b/Scripts/old/license-keygen.swift similarity index 100% rename from Scripts/license-keygen.swift rename to Scripts/old/license-keygen.swift diff --git a/Scripts/license-sign.swift b/Scripts/old/license-sign.swift similarity index 100% rename from Scripts/license-sign.swift rename to Scripts/old/license-sign.swift diff --git a/Scripts/release-preflight.sh b/Scripts/old/release-preflight.sh similarity index 100% rename from Scripts/release-preflight.sh rename to Scripts/old/release-preflight.sh diff --git a/Scripts/old/release.sh b/Scripts/old/release.sh new file mode 100755 index 0000000..327c28f --- /dev/null +++ b/Scripts/old/release.sh @@ -0,0 +1,206 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +PROJECT="$ROOT/Rsync Studio.xcodeproj" +SCHEME="Rsync Studio" +CONFIGURATION="Release" +PRODUCT_NAME="Rsync Studio" +DISPLAY_NAME="Rsync Studio" +BUNDLE_ID="net.absdev.RsyncStudio" +NOTARY_PROFILE="${NOTARY_PROFILE:-Rsync Studio Notary}" +RELEASE_ROOT="${RELEASE_ROOT:-$ROOT/build/releases}" +SKIP_PREFLIGHT=0 +SKIP_NOTARIZATION=0 + +usage() { + cat <&2; exit 2; }; NOTARY_PROFILE="$2"; shift 2 ;; + --output) [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; }; RELEASE_ROOT="$2"; shift 2 ;; + --skip-preflight) SKIP_PREFLIGHT=1; shift ;; + --skip-notarization) SKIP_NOTARIZATION=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; + esac +done + +[[ "$(uname -s)" == "Darwin" ]] || { echo "ERROR: release.sh must run on macOS." >&2; exit 2; } +for tool in xcodebuild xcrun hdiutil codesign spctl plutil shasum ditto; do + command -v "$tool" >/dev/null || { echo "ERROR: Missing required tool: $tool" >&2; exit 2; } +done + +PBX="$PROJECT/project.pbxproj" +VERSION="$(awk -F' = ' '/MARKETING_VERSION = / {gsub(/;/,"",$2); print $2; exit}' "$PBX")" +BUILD="$(awk -F' = ' '/CURRENT_PROJECT_VERSION = / {gsub(/;/,"",$2); print $2; exit}' "$PBX")" +[[ -n "$VERSION" && -n "$BUILD" ]] || { echo "ERROR: Could not determine version/build from Xcode project." >&2; exit 1; } + +RELEASE_DIR="$RELEASE_ROOT/$VERSION-$BUILD" +ARCHIVE="$RELEASE_DIR/$PRODUCT_NAME.xcarchive" +EXPORT_DIR="$RELEASE_DIR/export" +DMG_STAGING="$RELEASE_DIR/dmg-root" +DMG="$RELEASE_DIR/$PRODUCT_NAME-$VERSION.dmg" +CHECKSUM="$DMG.sha256" +METADATA="$RELEASE_DIR/release-info.txt" +EXPORT_OPTIONS="$RELEASE_DIR/ExportOptions.plist" + +if [[ -e "$RELEASE_DIR" ]]; then + echo "ERROR: Release directory already exists: $RELEASE_DIR" >&2 + echo "Refusing to overwrite an existing version/build artifact." >&2 + exit 1 +fi +mkdir -p "$RELEASE_DIR" + +cleanup() { + rm -rf "$DMG_STAGING" +} +trap cleanup EXIT + +if [[ "$SKIP_PREFLIGHT" -eq 0 ]]; then + "$SCRIPT_DIR/release-preflight.sh" +fi + +cat > "$EXPORT_OPTIONS" < + + + + method + developer-id + signingStyle + automatic + stripSwiftSymbols + + + +PLIST +plutil -lint "$EXPORT_OPTIONS" >/dev/null + +XCODE_SIGN_ARGS=() +if [[ -n "${DEVELOPMENT_TEAM:-}" ]]; then + XCODE_SIGN_ARGS+=("DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM") +fi + +echo "== Archive $DISPLAY_NAME $VERSION ($BUILD) ==" +xcodebuild \ + -project "$PROJECT" \ + -scheme "$SCHEME" \ + -configuration "$CONFIGURATION" \ + -destination 'generic/platform=macOS' \ + -archivePath "$ARCHIVE" \ + clean archive \ + "${XCODE_SIGN_ARGS[@]}" + +echo "== Export Developer ID app ==" +xcodebuild \ + -exportArchive \ + -archivePath "$ARCHIVE" \ + -exportPath "$EXPORT_DIR" \ + -exportOptionsPlist "$EXPORT_OPTIONS" \ + -allowProvisioningUpdates + +APP="$EXPORT_DIR/$PRODUCT_NAME.app" +[[ -d "$APP" ]] || { echo "ERROR: Exported app not found: $APP" >&2; exit 1; } + +ACTUAL_BUNDLE_ID="$(plutil -extract CFBundleIdentifier raw -o - "$APP/Contents/Info.plist" 2>/dev/null || true)" +[[ "$ACTUAL_BUNDLE_ID" == "$BUNDLE_ID" ]] || { echo "ERROR: Exported bundle id is '$ACTUAL_BUNDLE_ID', expected '$BUNDLE_ID'." >&2; exit 1; } +"$SCRIPT_DIR/verify-signed-app.sh" "$APP" + +if [[ "$SKIP_NOTARIZATION" -eq 0 ]]; then + echo "== Validate notarization credentials ==" + xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" >/dev/null + + echo "== Notarize exported app ==" + APP_ZIP="$RELEASE_DIR/$PRODUCT_NAME-$VERSION-notary.zip" + ditto -c -k --keepParent "$APP" "$APP_ZIP" + xcrun notarytool submit "$APP_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait + xcrun stapler staple "$APP" + xcrun stapler validate "$APP" + rm -f "$APP_ZIP" + "$SCRIPT_DIR/verify-signed-app.sh" "$APP" +fi + +echo "== Create drag-to-Applications DMG ==" +mkdir -p "$DMG_STAGING" +ditto "$APP" "$DMG_STAGING/$PRODUCT_NAME.app" +ln -s /Applications "$DMG_STAGING/Applications" +cat > "$DMG_STAGING/README.txt" </dev/null + +hdiutil verify "$DMG" >/dev/null + +if [[ "$SKIP_NOTARIZATION" -eq 0 ]]; then + echo "== Notarize final DMG ==" + xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait + xcrun stapler staple "$DMG" + xcrun stapler validate "$DMG" + + echo "== Final Gatekeeper verification ==" + "$SCRIPT_DIR/verify-signed-app.sh" "$APP" + spctl --assess --type open --context context:primary-signature --verbose=4 "$DMG" +else + echo "WARNING: Notarization was skipped. This output is NOT ready for public distribution." >&2 +fi + +printf '%s %s\n' "$(shasum -a 256 "$DMG" | awk '{print $1}')" "$(basename "$DMG")" > "$CHECKSUM" + +DMG_SIZE="$(stat -f '%z' "$DMG")" +DMG_SHA256="$(awk '{print $1}' "$CHECKSUM")" +cat > "$METADATA" <&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" -usage() { - cat </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; } -while [[ $# -gt 0 ]]; do - case "$1" in - --notary-profile) [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; }; NOTARY_PROFILE="$2"; shift 2 ;; - --output) [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 2; }; RELEASE_ROOT="$2"; shift 2 ;; - --skip-preflight) SKIP_PREFLIGHT=1; shift ;; - --skip-notarization) SKIP_NOTARIZATION=1; shift ;; - -h|--help) usage; exit 0 ;; - *) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;; - esac -done - -[[ "$(uname -s)" == "Darwin" ]] || { echo "ERROR: release.sh must run on macOS." >&2; exit 2; } -for tool in xcodebuild xcrun hdiutil codesign spctl plutil shasum ditto; do - command -v "$tool" >/dev/null || { echo "ERROR: Missing required tool: $tool" >&2; exit 2; } -done - -PBX="$PROJECT/project.pbxproj" -VERSION="$(awk -F' = ' '/MARKETING_VERSION = / {gsub(/;/,"",$2); print $2; exit}' "$PBX")" -BUILD="$(awk -F' = ' '/CURRENT_PROJECT_VERSION = / {gsub(/;/,"",$2); print $2; exit}' "$PBX")" -[[ -n "$VERSION" && -n "$BUILD" ]] || { echo "ERROR: Could not determine version/build from Xcode project." >&2; exit 1; } - -RELEASE_DIR="$RELEASE_ROOT/$VERSION-$BUILD" -ARCHIVE="$RELEASE_DIR/$PRODUCT_NAME.xcarchive" -EXPORT_DIR="$RELEASE_DIR/export" -DMG_STAGING="$RELEASE_DIR/dmg-root" -DMG="$RELEASE_DIR/$PRODUCT_NAME-$VERSION.dmg" -CHECKSUM="$DMG.sha256" -METADATA="$RELEASE_DIR/release-info.txt" -EXPORT_OPTIONS="$RELEASE_DIR/ExportOptions.plist" - -if [[ -e "$RELEASE_DIR" ]]; then - echo "ERROR: Release directory already exists: $RELEASE_DIR" >&2 - echo "Refusing to overwrite an existing version/build artifact." >&2 - exit 1 -fi -mkdir -p "$RELEASE_DIR" - -cleanup() { - rm -rf "$DMG_STAGING" -} -trap cleanup EXIT - -if [[ "$SKIP_PREFLIGHT" -eq 0 ]]; then - "$SCRIPT_DIR/release-preflight.sh" -fi - +cd "$ROOT_DIR" +rm -rf "$BUILD_DIR" +mkdir -p "$BUILD_DIR" "$EXPORT_DIR" "$DMG_STAGE" "$RELEASE_DIR" "$MOUNT_POINT" cat > "$EXPORT_OPTIONS" < - - - method - developer-id - signingStyle - automatic - stripSwiftSymbols - - - + +methoddeveloper-id +signingStyleautomatic +teamID$TEAM_ID + PLIST -plutil -lint "$EXPORT_OPTIONS" >/dev/null -XCODE_SIGN_ARGS=() -if [[ -n "${DEVELOPMENT_TEAM:-}" ]]; then - XCODE_SIGN_ARGS+=("DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM") +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" -echo "== Archive $DISPLAY_NAME $VERSION ($BUILD) ==" -xcodebuild \ - -project "$PROJECT" \ - -scheme "$SCHEME" \ - -configuration "$CONFIGURATION" \ - -destination 'generic/platform=macOS' \ - -archivePath "$ARCHIVE" \ - clean archive \ - "${XCODE_SIGN_ARGS[@]}" +# 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 "== Export Developer ID app ==" -xcodebuild \ - -exportArchive \ - -archivePath "$ARCHIVE" \ - -exportPath "$EXPORT_DIR" \ - -exportOptionsPlist "$EXPORT_OPTIONS" \ - -allowProvisioningUpdates +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" -APP="$EXPORT_DIR/$PRODUCT_NAME.app" -[[ -d "$APP" ]] || { echo "ERROR: Exported app not found: $APP" >&2; exit 1; } +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" -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 -ACTUAL_BUNDLE_ID="$(plutil -extract CFBundleIdentifier raw -o - "$APP/Contents/Info.plist" 2>/dev/null || true)" -[[ "$ACTUAL_BUNDLE_ID" == "$BUNDLE_ID" ]] || { echo "ERROR: Exported bundle id is '$ACTUAL_BUNDLE_ID', expected '$BUNDLE_ID'." >&2; exit 1; } -"$SCRIPT_DIR/verify-signed-app.sh" "$APP" +# 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 ") +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" -if [[ "$SKIP_NOTARIZATION" -eq 0 ]]; then - echo "== Validate notarization credentials ==" - xcrun notarytool history --keychain-profile "$NOTARY_PROFILE" >/dev/null +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" - echo "== Notarize exported app ==" - APP_ZIP="$RELEASE_DIR/$PRODUCT_NAME-$VERSION-notary.zip" - ditto -c -k --keepParent "$APP" "$APP_ZIP" - xcrun notarytool submit "$APP_ZIP" --keychain-profile "$NOTARY_PROFILE" --wait - xcrun stapler staple "$APP" - xcrun stapler validate "$APP" - rm -f "$APP_ZIP" - "$SCRIPT_DIR/verify-signed-app.sh" "$APP" -fi - -echo "== Create drag-to-Applications DMG ==" -mkdir -p "$DMG_STAGING" -ditto "$APP" "$DMG_STAGING/$PRODUCT_NAME.app" -ln -s /Applications "$DMG_STAGING/Applications" -cat > "$DMG_STAGING/README.txt" </dev/null - -hdiutil verify "$DMG" >/dev/null - -if [[ "$SKIP_NOTARIZATION" -eq 0 ]]; then - echo "== Notarize final DMG ==" - xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait - xcrun stapler staple "$DMG" - xcrun stapler validate "$DMG" - - echo "== Final Gatekeeper verification ==" - "$SCRIPT_DIR/verify-signed-app.sh" "$APP" - spctl --assess --type open --context context:primary-signature --verbose=4 "$DMG" -else - echo "WARNING: Notarization was skipped. This output is NOT ready for public distribution." >&2 -fi - -printf '%s %s\n' "$(shasum -a 256 "$DMG" | awk '{print $1}')" "$(basename "$DMG")" > "$CHECKSUM" - -DMG_SIZE="$(stat -f '%z' "$DMG")" -DMG_SHA256="$(awk '{print $1}' "$CHECKSUM")" -cat > "$METADATA" < Release ready:' +ls -lh "$DMG_PATH" +shasum -a 256 "$DMG_PATH" +printf '\n========== DBSyn RELEASE COMPLETE ==========\n%s\n' "$DMG_PATH"