new scripts
This commit is contained in:
Generated
+8
@@ -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
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/RsyncStudio.iml" filepath="$PROJECT_DIR$/.idea/RsyncStudio.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/rsync-studio-licensing" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Executable
+206
@@ -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 <<USAGE
|
||||
Usage: $(basename "$0") [options]
|
||||
|
||||
Builds a Developer ID archive, exports the signed app, creates a drag-to-Applications
|
||||
DMG, notarizes/staples it, verifies Gatekeeper, and writes SHA-256 checksums.
|
||||
Nothing is uploaded to your website or update feed.
|
||||
|
||||
Options:
|
||||
--notary-profile NAME Keychain profile created by notarytool (default: $NOTARY_PROFILE)
|
||||
--output DIR Release output root (default: build/releases)
|
||||
--skip-preflight Skip Scripts/release-preflight.sh
|
||||
--skip-notarization Build/sign/package only; do not submit to Apple
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
DEVELOPMENT_TEAM Optional Apple Developer Team ID. Recommended for CI.
|
||||
NOTARY_PROFILE Alternative to --notary-profile.
|
||||
RELEASE_ROOT Alternative to --output.
|
||||
|
||||
First-time notarization setup:
|
||||
xcrun notarytool store-credentials "$NOTARY_PROFILE"
|
||||
USAGE
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
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" <<TXT
|
||||
$DISPLAY_NAME $VERSION
|
||||
|
||||
Install: drag $DISPLAY_NAME to Applications.
|
||||
Then eject this disk image and launch $DISPLAY_NAME from Applications.
|
||||
TXT
|
||||
hdiutil create \
|
||||
-volname "$DISPLAY_NAME $VERSION" \
|
||||
-srcfolder "$DMG_STAGING" \
|
||||
-ov \
|
||||
-format UDZO \
|
||||
-imagekey zlib-level=9 \
|
||||
"$DMG" >/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" <<INFO
|
||||
Product: $DISPLAY_NAME
|
||||
Bundle ID: $BUNDLE_ID
|
||||
Version: $VERSION
|
||||
Build: $BUILD
|
||||
Artifact: $(basename "$DMG")
|
||||
Bytes: $DMG_SIZE
|
||||
SHA-256: $DMG_SHA256
|
||||
Notarized: $([[ "$SKIP_NOTARIZATION" -eq 0 ]] && echo yes || echo no)
|
||||
Notary profile: $NOTARY_PROFILE
|
||||
Generated (UTC): $(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
INFO
|
||||
|
||||
rm -f "$EXPORT_OPTIONS"
|
||||
|
||||
echo
|
||||
echo "Release artifact created successfully:"
|
||||
echo " $DMG"
|
||||
echo " $CHECKSUM"
|
||||
echo " $METADATA"
|
||||
echo
|
||||
echo "Publishing is intentionally NOT automated. Test the DMG on a clean Mac before upload."
|
||||
Executable → Regular
+139
-188
@@ -1,206 +1,157 @@
|
||||
#!/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
|
||||
# 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"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage: $(basename "$0") [options]
|
||||
|
||||
Builds a Developer ID archive, exports the signed app, creates a drag-to-Applications
|
||||
DMG, notarizes/staples it, verifies Gatekeeper, and writes SHA-256 checksums.
|
||||
Nothing is uploaded to your website or update feed.
|
||||
|
||||
Options:
|
||||
--notary-profile NAME Keychain profile created by notarytool (default: $NOTARY_PROFILE)
|
||||
--output DIR Release output root (default: build/releases)
|
||||
--skip-preflight Skip Scripts/release-preflight.sh
|
||||
--skip-notarization Build/sign/package only; do not submit to Apple
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
DEVELOPMENT_TEAM Optional Apple Developer Team ID. Recommended for CI.
|
||||
NOTARY_PROFILE Alternative to --notary-profile.
|
||||
RELEASE_ROOT Alternative to --output.
|
||||
|
||||
First-time notarization setup:
|
||||
xcrun notarytool store-credentials "$NOTARY_PROFILE"
|
||||
USAGE
|
||||
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;
|
||||
}
|
||||
|
||||
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" <<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>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
<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
|
||||
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 <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"
|
||||
|
||||
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" <<TXT
|
||||
$DISPLAY_NAME $VERSION
|
||||
|
||||
Install: drag $DISPLAY_NAME to Applications.
|
||||
Then eject this disk image and launch $DISPLAY_NAME from Applications.
|
||||
TXT
|
||||
hdiutil create \
|
||||
-volname "$DISPLAY_NAME $VERSION" \
|
||||
-srcfolder "$DMG_STAGING" \
|
||||
-ov \
|
||||
-format UDZO \
|
||||
-imagekey zlib-level=9 \
|
||||
"$DMG" >/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" <<INFO
|
||||
Product: $DISPLAY_NAME
|
||||
Bundle ID: $BUNDLE_ID
|
||||
Version: $VERSION
|
||||
Build: $BUILD
|
||||
Artifact: $(basename "$DMG")
|
||||
Bytes: $DMG_SIZE
|
||||
SHA-256: $DMG_SHA256
|
||||
Notarized: $([[ "$SKIP_NOTARIZATION" -eq 0 ]] && echo yes || echo no)
|
||||
Notary profile: $NOTARY_PROFILE
|
||||
Generated (UTC): $(date -u '+%Y-%m-%dT%H:%M:%SZ')
|
||||
INFO
|
||||
|
||||
rm -f "$EXPORT_OPTIONS"
|
||||
|
||||
echo
|
||||
echo "Release artifact created successfully:"
|
||||
echo " $DMG"
|
||||
echo " $CHECKSUM"
|
||||
echo " $METADATA"
|
||||
echo
|
||||
echo "Publishing is intentionally NOT automated. Test the DMG on a clean Mac before upload."
|
||||
# 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