updated release script with zsh guard and ony dmg in dist directory

This commit is contained in:
Mark Gregory
2026-11-03 15:30:17 +00:00
parent f23278eaa6
commit c35dd4b20e
4 changed files with 30 additions and 25 deletions
+1 -1
View File
@@ -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.
+5 -7
View File
@@ -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.
+3 -1
View File
@@ -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.
+21 -16
View File
@@ -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"