From c35dd4b20e72ce5adeae41a77330d643cd6c0d14 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Tue, 3 Nov 2026 15:30:17 +0000 Subject: [PATCH] updated release script with zsh guard and ony dmg in dist directory --- README.md | 2 +- docs/RELEASING.md | 12 +++++------- docs/VERIFICATION.md | 4 +++- scripts/release.sh | 37 +++++++++++++++++++++---------------- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 55839d6..a0b5765 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Data stays under `~/Library/Application Support/KJamsClone/` for continuity. Bac ./scripts/release.sh ``` -Release output: a branded DMG and app ZIP. Normal `./scripts/release.sh` requires Developer ID signing and a saved notarization profile, notarizes/staples both app and DMG, and requires Gatekeeper checks before publishing the final files. Run `./scripts/setup-notarization.sh` once in Terminal to save the default `KJMixPro` profile. For a deliberately unnotarized local build use `./scripts/release.sh --signed-only`; `--unsigned` creates an unsigned preview and the legacy `dist/KJMixPro-unsigned-macOS.zip`. See [release setup](docs/RELEASING.md). The approved icon, complete AppIcon set, high-resolution master and original generation prompt are included under Brand/assets. Bundle/module identifiers remain compatible internally; displayed branding is KJMix Pro. +Release output: only the current branded DMG in dist. Verification reports and the checksum are stored under build-release/release-verification. Normal `./scripts/release.sh` requires Developer ID signing and a saved notarization profile, notarizes/staples both app and DMG, and requires Gatekeeper checks before publishing the final files. Run `./scripts/setup-notarization.sh` once in Terminal to save the default `KJMixPro` profile. For a deliberately unnotarized local build use `./scripts/release.sh --signed-only`; `--unsigned` creates an unsigned preview DMG. See [release setup](docs/RELEASING.md). The approved icon, complete AppIcon set, high-resolution master and original generation prompt are included under Brand/assets. Bundle/module identifiers remain compatible internally; displayed branding is KJMix Pro. **73 XCTest cases passed**, including real loopback HTTP, access isolation/revocation, rotation, independent singer settings, fades, timed slides, batch atomicity, search, MP3/MP4 tag backup/media preservation and all earlier library/playback/waveform/venue/drop regressions. Release builds arm64 and x86_64, tested on Apple Silicon macOS 27.2 with Xcode 27.2/Swift 6.4.2. Deployment target macOS 13; this Xcode's XCTest runtime may require macOS 14+. Intel/older macOS execution is untested. See docs/VERIFICATION.md. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index 1c8a9d5..70e0301 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -28,13 +28,9 @@ If you already have a profile with a different name, use that name through `KJMI The script builds Release, stages a copy, signs the app with Developer ID and hardened runtime, submits it to Apple, checks Accepted status, staples/validates its ticket, and checks app Gatekeeper assessment. It then creates the compressed DMG with the colourful app/volume/file icon and Applications shortcut, signs and notarizes the DMG, staples/validates its ticket, checks its Gatekeeper assessment and mounts the finished image to recheck the enclosed app. A fresh-Mac launch/install check is still recommended before public distribution. -Outputs are versioned, for example: +The only file in dist is the current versioned DMG, for example `dist/KJMixPro-0.6.4-macOS.dmg`. After all checks pass, old DMGs, ZIPs, checksum sidecars and other output entries are removed. Reports and SHA-256 are kept outside dist under `build-release/release-verification/KJMixPro-0.6.4-macOS/`. The temporary ZIP required for app notarization stays in the private staging folder and is removed automatically. -- `dist/KJMixPro-0.6.4-macOS.dmg` -- `dist/KJMixPro-0.6.4-macOS.zip` (stapled app) -- SHA-256 sidecars and `dist/KJMixPro-0.6.4-macOS-verification/` reports - -The script publishes these files only after the checks succeed. A failed run returns nonzero and never labels that attempt Gatekeeper verified. Existing files from earlier builds can remain in dist; check the current report and successful script completion. +The script publishes the DMG and cleans old output only after the checks succeed. A failed run retains the existing distribution, returns nonzero and never labels that attempt Gatekeeper verified. You can configure another saved profile or signing identity without editing the script: @@ -52,6 +48,8 @@ The script auto-selects a Developer ID identity if all available matching certif ./scripts/release.sh --unsigned ``` -These modes have clearly marked filenames and status reports. **They are not notarized or Gatekeeper verified.** Signed-only does not need notarization credentials. Unsigned creates the compatibility ZIP as well. Neither mode changes Gatekeeper settings or bypasses macOS security. +These modes have clearly marked filenames and status reports. **They are not notarized or Gatekeeper verified.** Signed-only does not need notarization credentials. Both modes also keep only their current DMG in dist. Neither mode changes Gatekeeper settings or bypasses macOS security. References: [Apple notarization](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution), [packaging for distribution](https://developer.apple.com/documentation/xcode/packaging-mac-software-for-distribution). + +Run `./scripts/release.sh` or `bash ./scripts/release.sh`; do not source it into your interactive shell. Notarization uses `notary_status`, avoiding zsh’s read-only `status` variable. diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md index 7194aed..88ac5dc 100644 --- a/docs/VERIFICATION.md +++ b/docs/VERIFICATION.md @@ -8,7 +8,7 @@ Verified on 3 November 2026. - Release builds both arm64 and x86_64. Source ZIP includes canonical KJMix Pro.xcodeproj/project.pbxproj and shared scheme, source, tests, executable scripts, icon assets and original test media only. - ZIP CRC checks, extracted file hashes and extracted Xcode project listing are checked during packaging. Personal media, library databases, invitations, bookmarks and user settings are excluded. -The production DMG and app ZIP are Developer ID signed, notarized and stapled; explicit local preview modes remain unnotarized. Intel/older macOS runtime, broad commercial media, hardware/display latency, large-library show stress and every native drag/keyboard interaction are not exhaustively validated. Music-store profiles/catalogue imports are not provider purchase or subscription integrations. See README.md for feature limits. +The production DMG and its enclosed app are Developer ID signed, notarized and stapled; explicit local preview modes remain unnotarized. Intel/older macOS runtime, broad commercial media, hardware/display latency, large-library show stress and every native drag/keyboard interaction are not exhaustively validated. Music-store profiles/catalogue imports are not provider purchase or subscription integrations. See README.md for feature limits. The complete operating-system drag gesture is not automated end to end. Drop payload delivery and ordering are tested with real NSItemProviders, and visible reorder arrows provide an independently mouse-tested alternative. @@ -25,3 +25,5 @@ Batch waveform checks verify cache reuse versus forced regeneration, deduplicati The release script now creates a compressed DMG with the approved app icon as both its volume icon and Finder file icon, plus an Applications shortcut. A real `--signed-only` run succeeded with the installed Developer ID Application identity and hardened runtime. DMG checksum, mounted contents, custom icon flag, app/volume artwork matching, app signature and disk-image signature were verified. Gatekeeper assessments of both app and DMG explicitly returned `Unnotarized Developer ID`; this output is **not** a Gatekeeper-verified release. The normal release mode is fail-closed: it requires a valid notarytool Keychain profile, notarizes/staples the app and DMG, and requires Gatekeeper success for both the image and mounted app before publishing verified filenames. The missing-profile failure was verified before credential setup. After the `KJMixPro` profile was configured, Apple accepted both the app and DMG, both tickets were stapled and validated, and Gatekeeper accepted the final DMG and mounted app as `Notarized Developer ID`. The app also passed `syspolicy_check distribution`. An independent final check confirmed the downloadable DMG/app tickets, extracted ZIP app signature, ZIP integrity and download SHA-256 hashes. See RELEASING.md. + +The release script now keeps only the current DMG in dist. Reports/checksums are written under build-release/release-verification, and old outputs are removed only after the new image passes its checks. The notarization result variable is notary_status; Bash/zsh function regressions verified Accepted and Invalid responses without the zsh read-only status error. diff --git a/scripts/release.sh b/scripts/release.sh index bb1db4e..341727a 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -1,4 +1,11 @@ #!/bin/bash +# Also honor the Bash requirement if launched explicitly as `zsh scripts/release.sh`. +if [ -z "${BASH_VERSION:-}" ]; then + case "${ZSH_EVAL_CONTEXT:-}" in + *:file) printf 'Run this script with: bash ./scripts/release.sh\n' >&2;return 1 ;; + *) exec /bin/bash "$0" "$@" ;; + esac +fi set -euo pipefail cd "$(dirname "$0")/.." @@ -7,9 +14,10 @@ usage() { Usage: ./scripts/release.sh [--signed-only | --unsigned] [--] [xcodebuild arguments] Default: Developer ID sign, notarize and staple the app and branded DMG, then - require successful Gatekeeper checks before publishing the files. + require successful Gatekeeper checks before publishing the DMG. --signed-only: Build a Developer ID signed DMG without notarization. ---unsigned: Build an unsigned preview DMG and the legacy unsigned ZIP. +--unsigned: Build an unsigned preview DMG. +Only the current DMG is kept in dist; reports stay under build-release/release-verification. KJMIX_SIGNING_IDENTITY: Developer ID Application certificate name or SHA-1. Auto-selects when all matching certificates have one name. @@ -61,6 +69,7 @@ release_built="$PWD/build-release/Build/Products/Release/KJMix Pro.app" release_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$release_built/Contents/Info.plist")" [[ "$release_version" =~ ^[A-Za-z0-9._-]+$ ]] || fail 'Invalid release version.' release_payload="$release_work/payload" +[[ ! -L "$PWD/dist" ]] || fail 'dist must be a real directory, not a symbolic link.' /bin/mkdir -p "$release_payload" dist release_app="$release_payload/KJMix Pro.app" /usr/bin/ditto --noextattr --norsrc "$release_built" "$release_app" @@ -73,21 +82,21 @@ release_suffix=macOS if [[ "$release_mode" == unsigned ]]; then release_suffix=unsigned-macOS;fi if [[ "$release_mode" == signed ]]; then release_suffix=signed-unnotarized-macOS;fi release_name="KJMixPro-$release_version-$release_suffix" -release_reports="$PWD/dist/$release_name-verification" +release_reports="$PWD/build-release/release-verification/$release_name" /bin/mkdir -p "$release_reports" printf 'Preparing release. NOT yet verified.\n' > "$release_reports/STATUS.txt" notarize() { - local artifact="$1" label="$2" report="$release_reports/$2-notary.json" submission status + local artifact="$1" label="$2" report="$release_reports/$2-notary.json" submission notary_status printf 'Submitting %s to Apple notarization…\n' "$label" if ! xcrun notarytool submit "$artifact" --keychain-profile "$release_profile" --wait --timeout 30m --output-format json > "$report"; then printf 'Notarization did not complete. Inspect %s; Apple may still be processing the submission.\n' "$report" >&2 return 1 fi - status="$(/usr/bin/plutil -extract status raw -o - "$report")" + notary_status="$(/usr/bin/plutil -extract status raw -o - "$report")" submission="$(/usr/bin/plutil -extract id raw -o - "$report")" - if [[ "$status" != Accepted ]]; then + if [[ "$notary_status" != Accepted ]]; then xcrun notarytool log "$submission" --keychain-profile "$release_profile" "$release_reports/$label-notary-log.json" || true - fail "Apple did not accept $label (status: $status). See $release_reports." + fail "Apple did not accept $label (status: $notary_status). See $release_reports." fi } if [[ "$release_mode" != unsigned ]]; then @@ -140,19 +149,15 @@ if [[ "$release_mode" == notarized ]]; then fi /usr/bin/hdiutil detach -quiet "$release_mount" release_mounted=false -/usr/bin/ditto -c -k --sequesterRsrc --keepParent "$release_app" "$release_work/$release_name.zip" /bin/mv -f "$release_dmg" "dist/$release_name.dmg" -/bin/mv -f "$release_work/$release_name.zip" "dist/$release_name.zip" -if [[ "$release_mode" == unsigned ]]; then /bin/cp "dist/$release_name.zip" dist/KJMixPro-unsigned-macOS.zip;fi -( - cd dist - /usr/bin/shasum -a 256 "$release_name.dmg" > "$release_name.dmg.sha256" - /usr/bin/shasum -a 256 "$release_name.zip" > "$release_name.zip.sha256" -) +# Preserve the previous distribution until the new image has passed every check. +# Nothing other than the current DMG belongs in this generated output directory. +/usr/bin/find "$PWD/dist" -mindepth 1 -maxdepth 1 ! -name "$release_name.dmg" -exec /bin/rm -rf -- {} + +/usr/bin/shasum -a 256 "dist/$release_name.dmg" > "$release_reports/SHA256.txt" if [[ "$release_mode" == notarized ]]; then printf 'Developer ID signed; Apple notarization accepted; app and DMG tickets stapled; Gatekeeper checks passed.\n' | /usr/bin/tee "$release_reports/STATUS.txt" else printf '%s build only. NOT notarized or Gatekeeper verified.\n' "$release_mode" | /usr/bin/tee "$release_reports/STATUS.txt" fi release_completed=true -printf 'Created dist/%s.dmg and dist/%s.zip\n' "$release_name" "$release_name" +printf 'Created dist/%s.dmg\nVerification reports: %s\n' "$release_name" "$release_reports"