From 2efeaf076407de2255dbcd5b9b5c4a778e9b5176 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Wed, 21 Oct 2026 14:08:51 +0100 Subject: [PATCH] updates --- .idea/vcs.xml | 6 +++++ Scripts/build.sh | 24 +++++++++++++++++ Scripts/release.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++++ docs/README.md | 16 +++++++++++- 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 .idea/vcs.xml create mode 100755 Scripts/build.sh create mode 100755 Scripts/release.sh diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Scripts/build.sh b/Scripts/build.sh new file mode 100755 index 0000000..8a3d0e9 --- /dev/null +++ b/Scripts/build.sh @@ -0,0 +1,24 @@ +#!/bin/zsh +set -euo pipefail + +SCRIPT_DIR="${0:A:h}" +ROOT="${SCRIPT_DIR:h}" +PROJECT="$ROOT/PegasusMonitor.xcodeproj" +SCHEME="PegasusMonitor" +DERIVED="$ROOT/.build/DerivedData" +CONFIGURATION="${CONFIGURATION:-Debug}" +APP="$DERIVED/Build/Products/$CONFIGURATION/PegasusMonitor.app" + +[[ -d "$PROJECT" ]] || { print -u2 "Missing Xcode project: $PROJECT"; exit 1; } +command -v xcodebuild >/dev/null || { print -u2 'xcodebuild not found; install Xcode and select it with xcode-select.'; exit 1; } + +print "Building $SCHEME ($CONFIGURATION)..." +xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration "$CONFIGURATION" \ + -derivedDataPath "$DERIVED" -destination 'platform=macOS' \ + CODE_SIGNING_ALLOWED=NO build + +[[ -d "$APP" ]] || { print -u2 "Build finished but application was not found: $APP"; exit 1; } +print "Build successful: $APP" +if [[ "${LAUNCH_AFTER_BUILD:-1}" == 1 ]]; then + open "$APP" +fi diff --git a/Scripts/release.sh b/Scripts/release.sh new file mode 100755 index 0000000..7ab5961 --- /dev/null +++ b/Scripts/release.sh @@ -0,0 +1,64 @@ +#!/bin/zsh +set -euo pipefail + +SCRIPT_DIR="${0:A:h}" +ROOT="${SCRIPT_DIR:h}" +PROJECT="$ROOT/PegasusMonitor.xcodeproj" +SCHEME="PegasusMonitor" +DERIVED="$ROOT/.build/ReleaseDerivedData" +APP="$DERIVED/Build/Products/Release/PegasusMonitor.app" +DIST="$ROOT/dist" + +[[ -d "$PROJECT" ]] || { print -u2 "Missing Xcode project: $PROJECT"; exit 1; } +for cmd in xcodebuild codesign hdiutil PlistBuddy; do + command -v "$cmd" >/dev/null || { print -u2 "Missing command: $cmd"; exit 1; } +done + +# Choose a Developer ID Application identity unless explicitly provided. +IDENTITY="${SIGNING_IDENTITY:-}" +if [[ -z "$IDENTITY" ]]; then + IDENTITY="$(security find-identity -v -p codesigning 2>/dev/null | sed -nE 's/^ *[0-9]+\) [A-F0-9]+ "(Developer ID Application: [^"]+)".*/\1/p' | head -n 1)" +fi +if [[ -z "$IDENTITY" && "${ALLOW_UNSIGNED:-0}" != 1 ]]; then + print -u2 'No Developer ID Application identity found. Set SIGNING_IDENTITY or use ALLOW_UNSIGNED=1 for a local test package.' + exit 1 +fi + +print "Building $SCHEME (Release)..." +xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \ + -derivedDataPath "$DERIVED" -destination 'platform=macOS' \ + CODE_SIGNING_ALLOWED=NO build +[[ -d "$APP" ]] || { print -u2 "Missing built application: $APP"; exit 1; } + +VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$APP/Contents/Info.plist")" +BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$APP/Contents/Info.plist")" +NAME="PegasusMonitor-${VERSION}-${BUILD}" +mkdir -p "$DIST" +DMG="$DIST/$NAME.dmg" + +if [[ -n "$IDENTITY" ]]; then + print "Signing application with: $IDENTITY" + codesign --force --deep --options runtime --timestamp --sign "$IDENTITY" "$APP" + codesign --verify --deep --strict --verbose=2 "$APP" +else + print 'WARNING: creating unsigned local-test release; distribution outside this Mac is not recommended.' +fi + +STAGE="$(mktemp -d)" +trap 'rm -rf "$STAGE"' EXIT +cp -R "$APP" "$STAGE/PegasusMonitor.app" +ln -s /Applications "$STAGE/Applications" +rm -f "$DMG" +hdiutil create -volname "Pegasus Monitor" -srcfolder "$STAGE" -format UDZO -ov "$DMG" >/dev/null + +# Optional Apple notary profile, previously saved with `xcrun notarytool store-credentials`. +if [[ -n "${NOTARY_PROFILE:-}" ]]; then + [[ -n "$IDENTITY" ]] || { print -u2 'Notarization requires a signed app.'; exit 1; } + print "Submitting for notarization with profile: $NOTARY_PROFILE" + xcrun notarytool submit "$DMG" --keychain-profile "$NOTARY_PROFILE" --wait + xcrun stapler staple "$DMG" + xcrun stapler validate "$DMG" +fi + +print "Release: $DMG" +print "Version: $VERSION ($BUILD)" diff --git a/docs/README.md b/docs/README.md index 7744c06..c0b9c0e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -31,7 +31,7 @@ Use SSH keys and trust the host key from an interactive SSH login before first r ## Build on macOS -Open `PegasusMonitor.xcodeproj` in Xcode and choose the PegasusMonitor scheme. Set your signing team as appropriate, then Build & Run. The app requires macOS 14.0 or newer. No third-party dependencies, build/release scripts, or packages are required. +Open `PegasusMonitor.xcodeproj` in Xcode and choose the PegasusMonitor scheme. Set your signing team as appropriate, then Build & Run. The app requires macOS 14.0 or newer. No third-party dependencies are required. The app appears as a status icon in the macOS menu bar, not the Dock. It polls every five minutes by default (configurable), shows 14 aggregated categories and per-check diagnostics, and sends notifications when a previously healthy server becomes unhealthy. The server JSON adapter is read-only; it never restarts services. @@ -50,3 +50,17 @@ The app appears as a status icon in the macOS menu bar, not the Dock. It polls e `MonitorMenuView.swift`: summary and drill-down details. `SettingsView.swift`: SSH configuration and interval. `server/pegasus-health-json`: read-only JSON adapter for the existing health script. + +## Build and release scripts + +Run from the project root: + +```zsh +./Scripts/build.sh # Debug build; launches the app +LAUNCH_AFTER_BUILD=0 ./Scripts/build.sh # Debug build without launching +./Scripts/release.sh # Signed Release DMG in dist/ +NOTARY_PROFILE=your-profile ./Scripts/release.sh # Notarize + staple DMG +ALLOW_UNSIGNED=1 ./Scripts/release.sh # Local testing only +``` + +Release automatically selects the first `Developer ID Application` identity from the login keychain, or honors `SIGNING_IDENTITY`. It requires an identity unless `ALLOW_UNSIGNED=1`. Signing and notarization do **not** happen in `build.sh`. Script-built artifacts are written to `.build/` and `dist/`. The DMG is assembled after successful compilation and signing. These scripts do not change Xcode project version numbers; change `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` in Xcode before a release.