updated release script
This commit is contained in:
@@ -105,7 +105,7 @@ Data stays under `~/Library/Application Support/KJamsClone/` for continuity. Bac
|
||||
./scripts/release.sh
|
||||
```
|
||||
|
||||
Release output: `dist/KJMixPro-unsigned-macOS.zip`. 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: 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.
|
||||
|
||||
**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.
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# Create a signed, notarized KJMix Pro DMG
|
||||
|
||||
Run these commands from the project folder in Terminal. Do not paste account passwords, private keys or app-specific passwords into chat or source files.
|
||||
|
||||
## One-time setup
|
||||
|
||||
Install a **Developer ID Application** certificate and its private key in your login Keychain. Apple Development certificates are for development, not this distribution workflow. View available signing identities with:
|
||||
|
||||
```sh
|
||||
security find-identity -v -p codesigning
|
||||
```
|
||||
|
||||
Save notarization credentials interactively:
|
||||
|
||||
```sh
|
||||
./scripts/setup-notarization.sh
|
||||
```
|
||||
|
||||
The helper runs `xcrun notarytool store-credentials KJMixPro`. Apple prompts for the developer Apple ID, team ID and an app-specific password, then validates and saves the credentials in Keychain. `notarytool` alone may not be on PATH: use `xcrun notarytool`, or the helper.
|
||||
|
||||
If you already have a profile with a different name, use that name through `KJMIX_NOTARY_PROFILE`. The profile name is a label you chose when saving the credentials; “Keychain” is not automatically a profile name.
|
||||
|
||||
## Verified release
|
||||
|
||||
```sh
|
||||
./scripts/release.sh
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
- `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.
|
||||
|
||||
You can configure another saved profile or signing identity without editing the script:
|
||||
|
||||
```sh
|
||||
KJMIX_NOTARY_PROFILE='your-profile' ./scripts/release.sh
|
||||
KJMIX_SIGNING_IDENTITY='certificate SHA-1' ./scripts/release.sh
|
||||
```
|
||||
|
||||
The script auto-selects a Developer ID identity if all available matching certificates share the same name; otherwise it asks for an explicit identity. It forwards remaining arguments to xcodebuild. Signing/notarization require network access and may take several minutes; reports retain the submission IDs if Apple processing times out.
|
||||
|
||||
## Explicit local previews
|
||||
|
||||
```sh
|
||||
./scripts/release.sh --signed-only
|
||||
./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.
|
||||
|
||||
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).
|
||||
@@ -19,3 +19,9 @@ Configurable background fade times persist; tests inspect the real mixer output
|
||||
Fade-out handoff regression checks inspect actual mixer gain while it decreases, assert the singer audio node has not started during the fade and starts after silence, and verify Pause/unload cancel the pending singer start. Manual background fade-out also has an intermediate gain check.
|
||||
|
||||
Batch waveform checks verify cache reuse versus forced regeneration, deduplication of tracks sharing audio, missing-file reporting, ShowScreen skipping, cancellation retaining completed caches, selected-track versus whole-library scope, and unchanged playback/history/library metadata. The release contains no Spotify support.
|
||||
|
||||
## DMG release pipeline
|
||||
|
||||
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. Neither `keychain` nor the default `KJMixPro` profile was configured during these checks, so the positive notarization/Gatekeeper path remains unverified pending the user’s one-time credential setup. The missing-profile failure was verified. See RELEASING.md.
|
||||
|
||||
+154
-2
@@ -1,6 +1,158 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
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.
|
||||
--signed-only: Build a Developer ID signed DMG without notarization.
|
||||
--unsigned: Build an unsigned preview DMG and the legacy unsigned ZIP.
|
||||
|
||||
KJMIX_SIGNING_IDENTITY: Developer ID Application certificate name or SHA-1.
|
||||
Auto-selects when all matching certificates have one name.
|
||||
KJMIX_NOTARY_PROFILE: notarytool Keychain profile (default: KJMixPro).
|
||||
Save credentials once with ./scripts/setup-notarization.sh in Terminal.
|
||||
EOF
|
||||
}
|
||||
release_mode=notarized
|
||||
case "${1:-}" in
|
||||
--help|-h) usage; exit 0 ;;
|
||||
--signed-only) release_mode=signed;shift ;;
|
||||
--unsigned) release_mode=unsigned;shift ;;
|
||||
esac
|
||||
if [[ "${1:-}" == -- ]]; then shift; fi
|
||||
release_work="$(mktemp -d "${TMPDIR:-/tmp}/kjmix-release.XXXXXX")"
|
||||
release_mount="$release_work/mounted"
|
||||
release_mounted=false
|
||||
release_completed=false
|
||||
release_reports=""
|
||||
cleanup() {
|
||||
if [[ "$release_completed" != true && -n "$release_reports" && -d "$release_reports" ]]; then printf 'FAILED or interrupted. This attempt is NOT a verified release.\n' > "$release_reports/STATUS.txt";fi
|
||||
if [[ "$release_mounted" == true ]]; then /usr/bin/hdiutil detach "$release_mount" -quiet || true; fi
|
||||
/bin/rm -rf "$release_work"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
fail() { printf 'Release failed: %s\n' "$*" >&2;exit 1; }
|
||||
release_identity="${KJMIX_SIGNING_IDENTITY:-}"
|
||||
release_profile="${KJMIX_NOTARY_PROFILE:-KJMixPro}"
|
||||
if [[ "$release_mode" != unsigned ]]; then
|
||||
release_identities="$(/usr/bin/security find-identity -v -p codesigning)"
|
||||
if [[ -z "$release_identity" ]]; then
|
||||
release_names="$(printf '%s\n' "$release_identities" | /usr/bin/sed -n 's/.*"\(Developer ID Application:.*\)"/\1/p' | /usr/bin/sort -u)"
|
||||
[[ -n "$release_names" ]] || fail 'No Developer ID Application certificate with a private key is available. Use --unsigned only for a local preview.'
|
||||
[[ "$(printf '%s\n' "$release_names" | /usr/bin/wc -l | /usr/bin/tr -d ' ')" == 1 ]] || fail 'Multiple Developer ID identities found. Set KJMIX_SIGNING_IDENTITY to the intended certificate SHA-1.'
|
||||
release_identity="$(printf '%s\n' "$release_identities" | /usr/bin/awk '/"Developer ID Application:/ {print $2;exit}')"
|
||||
fi
|
||||
printf 'Signing identity: %s\n' "$release_identity"
|
||||
fi
|
||||
if [[ "$release_mode" == notarized ]]; then
|
||||
if ! xcrun notarytool history --keychain-profile "$release_profile" --output-format json > "$release_work/profile-check.json" 2> "$release_work/profile-error.txt"; then
|
||||
/bin/cat "$release_work/profile-error.txt" >&2
|
||||
fail "Notarization profile '$release_profile' is unavailable or Apple could not validate it. Run ./scripts/setup-notarization.sh, or explicitly use --signed-only for an unnotarized build."
|
||||
fi
|
||||
fi
|
||||
|
||||
xcodebuild -project "KJMix Pro.xcodeproj" -scheme "KJMix Pro" -configuration Release -destination 'platform=macOS' -derivedDataPath "$PWD/build-release" CODE_SIGNING_ALLOWED=NO build "$@"
|
||||
mkdir -p dist
|
||||
/usr/bin/ditto -c -k --sequesterRsrc --keepParent "build-release/Build/Products/Release/KJMix Pro.app" dist/KJMixPro-unsigned-macOS.zip
|
||||
release_built="$PWD/build-release/Build/Products/Release/KJMix Pro.app"
|
||||
[[ -d "$release_built" ]] || fail 'Release app was not produced.'
|
||||
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"
|
||||
/bin/mkdir -p "$release_payload" dist
|
||||
release_app="$release_payload/KJMix Pro.app"
|
||||
/usr/bin/ditto --noextattr --norsrc "$release_built" "$release_app"
|
||||
release_icon="$release_app/Contents/Resources/AppIcon.icns"
|
||||
[[ -s "$release_icon" ]] || fail 'AppIcon.icns is missing.'
|
||||
# The app icon is already in its bundle; use the same approved artwork for the volume.
|
||||
/bin/cp "$release_icon" "$release_payload/.VolumeIcon.icns"
|
||||
/bin/ln -s /Applications "$release_payload/Applications"
|
||||
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"
|
||||
/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
|
||||
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")"
|
||||
submission="$(/usr/bin/plutil -extract id raw -o - "$report")"
|
||||
if [[ "$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."
|
||||
fi
|
||||
}
|
||||
if [[ "$release_mode" != unsigned ]]; then
|
||||
/usr/bin/codesign --force --options runtime --timestamp --sign "$release_identity" "$release_app"
|
||||
/usr/bin/codesign --verify --deep --strict --verbose=2 "$release_app" 2> "$release_reports/app-signature.txt"
|
||||
/usr/bin/codesign --display --verbose=4 "$release_app" 2> "$release_reports/app-signing-details.txt"
|
||||
# A development/ad-hoc certificate must never be presented as a distribution identity.
|
||||
/usr/bin/awk '/^Authority=Developer ID Application:/ {ok=1} END {exit !ok}' "$release_reports/app-signing-details.txt" || fail 'The app was not signed with Developer ID Application.'
|
||||
fi
|
||||
if [[ "$release_mode" == notarized ]]; then
|
||||
/usr/bin/ditto -c -k --sequesterRsrc --keepParent "$release_app" "$release_work/app-for-notarization.zip"
|
||||
notarize "$release_work/app-for-notarization.zip" app
|
||||
xcrun stapler staple "$release_app" > "$release_reports/app-staple.txt" 2>&1
|
||||
xcrun stapler validate "$release_app" >> "$release_reports/app-staple.txt" 2>&1
|
||||
/usr/sbin/spctl --assess --type execute --verbose=4 "$release_app" > "$release_reports/app-gatekeeper.txt" 2>&1
|
||||
if [[ -x /usr/bin/syspolicy_check ]]; then /usr/bin/syspolicy_check distribution "$release_app" > "$release_reports/app-distribution.txt" 2>&1;fi
|
||||
fi
|
||||
# Configure the custom volume icon on a writable image, then compress for distribution.
|
||||
/usr/bin/hdiutil create -quiet -srcfolder "$release_payload" -volname 'KJMix Pro' -fs HFS+ -format UDRW "$release_work/writable.dmg"
|
||||
/bin/mkdir -p "$release_mount"
|
||||
/usr/bin/hdiutil attach -quiet -readwrite -nobrowse -noautoopen -mountpoint "$release_mount" "$release_work/writable.dmg"
|
||||
release_mounted=true
|
||||
xcrun SetFile -a C "$release_mount"
|
||||
/usr/bin/hdiutil detach -quiet "$release_mount"
|
||||
release_mounted=false
|
||||
release_dmg="$release_work/$release_name.dmg"
|
||||
/usr/bin/hdiutil convert -quiet "$release_work/writable.dmg" -format UDZO -imagekey zlib-level=9 -o "$release_dmg"
|
||||
# Set the DMG file's Finder icon before signing the image.
|
||||
xcrun swift scripts/set-dmg-icon.swift "$release_icon" "$release_dmg"
|
||||
if [[ "$release_mode" != unsigned ]]; then
|
||||
/usr/bin/codesign --force --timestamp --sign "$release_identity" "$release_dmg"
|
||||
/usr/bin/codesign --verify --strict --verbose=2 "$release_dmg" 2> "$release_reports/dmg-signature.txt"
|
||||
fi
|
||||
if [[ "$release_mode" == notarized ]]; then
|
||||
notarize "$release_dmg" dmg
|
||||
xcrun stapler staple "$release_dmg" > "$release_reports/dmg-staple.txt" 2>&1
|
||||
xcrun stapler validate "$release_dmg" >> "$release_reports/dmg-staple.txt" 2>&1
|
||||
/usr/sbin/spctl --assess --type open --context context:primary-signature --verbose=4 "$release_dmg" > "$release_reports/dmg-gatekeeper.txt" 2>&1
|
||||
fi
|
||||
/usr/bin/hdiutil verify "$release_dmg" > "$release_reports/dmg-integrity.txt" 2>&1
|
||||
# Inspect the final compressed image, not only the source staging folder.
|
||||
/usr/bin/hdiutil attach -quiet -readonly -nobrowse -noautoopen -mountpoint "$release_mount" "$release_dmg"
|
||||
release_mounted=true
|
||||
[[ -s "$release_mount/.VolumeIcon.icns" && -L "$release_mount/Applications" && -d "$release_mount/KJMix Pro.app" ]] || fail 'DMG app, Applications shortcut or volume icon is missing.'
|
||||
[[ "$(/usr/bin/readlink "$release_mount/Applications")" == /Applications ]] || fail 'Applications shortcut target is incorrect.'
|
||||
if [[ "$release_mode" != unsigned ]]; then /usr/bin/codesign --verify --deep --strict "$release_mount/KJMix Pro.app" 2> "$release_reports/mounted-app-signature.txt";fi
|
||||
if [[ "$release_mode" == notarized ]]; then
|
||||
xcrun stapler validate "$release_mount/KJMix Pro.app" > "$release_reports/mounted-app-staple.txt" 2>&1
|
||||
/usr/sbin/spctl --assess --type execute --verbose=4 "$release_mount/KJMix Pro.app" > "$release_reports/mounted-app-gatekeeper.txt" 2>&1
|
||||
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"
|
||||
)
|
||||
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"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import AppKit
|
||||
// Reuse the approved application icon; no artwork or user files are altered.
|
||||
guard CommandLine.arguments.count == 3,
|
||||
let icon = NSImage(contentsOfFile:CommandLine.arguments[1]),
|
||||
NSWorkspace.shared.setIcon(icon,forFile:CommandLine.arguments[2],options:[]) else {
|
||||
fputs("Could not set the disk image Finder icon.\n",stderr)
|
||||
exit(1)
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
notary_profile="${1:-KJMixPro}"
|
||||
printf 'Save Apple notarization credentials as Keychain profile "%s".\n' "$notary_profile"
|
||||
printf 'Apple will prompt for your developer Apple ID, team ID and an app-specific password.\n'
|
||||
printf 'Enter them in this Terminal session, not in chat or source code.\n'
|
||||
xcrun notarytool store-credentials "$notary_profile"
|
||||
printf 'Saved. Run ./scripts/release.sh to build, sign, notarize and verify the DMG.\n'
|
||||
Reference in New Issue
Block a user