Files
Marko Lisica 678d8b77ff Add notify command to Fleet Desktop for patch notification toasts (#50211)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** #49325

# Checklist for submitter

- [x] Timeouts are implemented and retries are limited to avoid infinite
loops

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

## fleetd/orbit/Fleet Desktop

- [x] If the change applies to only one platform, confirmed that
`runtime.GOOS` is used as needed to isolate changes

macOS-only by construction — this is the native macOS app, which has no
other platform build.

No changes file: this app ships on its own release channel
(fleet-desktop-macos-v* → download.fleetdm.com), not the Fleet server
changelog, matching every previous PR to apps/fleet-desktop-macos/.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added macOS notification support through the `notify` command.
* Notifications appear as bottom-right toast messages with loading,
dismissal, timeout, and error handling.
* Added URL validation, clear usage guidance, and meaningful
command-line exit statuses.
  * Notification commands can run without opening the main app.

* **Bug Fixes**
* Improved single-instance handling for background notification
processes.
  * Improved detection of Fleet-rendered error pages.

* **Tests**
* Added developer tools for notification smoke testing and local
invocation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-04 14:54:10 +02:00

164 lines
7.2 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SRC_DIR="$SCRIPT_DIR/FleetDesktop"
EXT_SRC_DIR="$SCRIPT_DIR/FleetPSSOExtension"
BUILD_DIR="$SCRIPT_DIR/build"
APP_DIR="$BUILD_DIR/Fleet Desktop.app"
CONTENTS_DIR="$APP_DIR/Contents"
MACOS_DIR="$CONTENTS_DIR/MacOS"
APPEX_DIR="$CONTENTS_DIR/PlugIns/FleetPSSOExtension.appex"
APPEX_CONTENTS_DIR="$APPEX_DIR/Contents"
APPEX_MACOS_DIR="$APPEX_CONTENTS_DIR/MacOS"
# --- Identity & signing (all optional; production defaults) -----------------
# For local development a contributor signs under their own (non-production)
# Apple Developer team, which owns different bundle IDs than Fleet's. Override
# these to produce a dev-signed bundle. Left unset, the script behaves exactly
# as before: a compile-only bundle that CI signs separately with Fleet's certs.
# See the "Local PSSO development" contributor guide.
APP_BUNDLE_ID="${APP_BUNDLE_ID:-com.fleetdm.fleet-desktop}"
EXT_BUNDLE_ID="${EXT_BUNDLE_ID:-com.fleetdm.fleet-desktop.pssoextension}"
TEAM_ID="${TEAM_ID:-8VBZ3948LU}"
SIGNING_IDENTITY="${SIGNING_IDENTITY:-}" # e.g. "Apple Development: you@example.com (XXXXXXXXXX)"; empty => no signing
APP_PROFILE="${APP_PROFILE:-}" # path to the host-app .provisionprofile (required to sign)
EXT_PROFILE="${EXT_PROFILE:-}" # path to the extension .provisionprofile (required to sign)
echo "Building Fleet Desktop..."
rm -rf "$BUILD_DIR"
mkdir -p "$MACOS_DIR"
SDK="$(xcrun --show-sdk-path)"
# --- Host app -------------------------------------------------------------
SOURCES=(
"$SRC_DIR/cli.swift"
"$SRC_DIR/FleetErrorPage.swift"
"$SRC_DIR/FleetService.swift"
"$SRC_DIR/BrowserWindow.swift"
"$SRC_DIR/ToastWindow.swift"
"$SRC_DIR/NotifyCommand.swift"
"$SRC_DIR/FleetDesktopApp.swift"
)
SWIFT_FLAGS=(-sdk "$SDK" -parse-as-library -O)
# Build both architectures in parallel. Collect both exit statuses before
# failing — a bare `wait PID` under set -e exits on the first failure and
# leaves the other swiftc running in the background.
swiftc -target arm64-apple-macos13 "${SWIFT_FLAGS[@]}" \
-o "$BUILD_DIR/FleetDesktop-arm64" "${SOURCES[@]}" &
ARM64_PID=$!
swiftc -target x86_64-apple-macos13 "${SWIFT_FLAGS[@]}" \
-o "$BUILD_DIR/FleetDesktop-x86_64" "${SOURCES[@]}" &
X86_64_PID=$!
ARM64_STATUS=0
X86_64_STATUS=0
wait "$ARM64_PID" || ARM64_STATUS=$?
wait "$X86_64_PID" || X86_64_STATUS=$?
if [ "$ARM64_STATUS" -ne 0 ] || [ "$X86_64_STATUS" -ne 0 ]; then
echo "swiftc failed (arm64 exit $ARM64_STATUS, x86_64 exit $X86_64_STATUS)" >&2
exit 1
fi
lipo -create \
"$BUILD_DIR/FleetDesktop-arm64" \
"$BUILD_DIR/FleetDesktop-x86_64" \
-output "$MACOS_DIR/FleetDesktop"
rm "$BUILD_DIR/FleetDesktop-arm64" "$BUILD_DIR/FleetDesktop-x86_64"
# Copy Info.plist
cp "$SRC_DIR/Info.plist" "$CONTENTS_DIR/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $APP_BUNDLE_ID" "$CONTENTS_DIR/Info.plist"
# Copy app icon and Fleet logo into Resources
mkdir -p "$CONTENTS_DIR/Resources"
cp "$SRC_DIR/AppIcon.icns" "$CONTENTS_DIR/Resources/AppIcon.icns"
if [ -f "$SRC_DIR/fleet-logo.png" ]; then
cp "$SRC_DIR/fleet-logo.png" "$CONTENTS_DIR/Resources/fleet-logo.png"
fi
# --- Platform SSO extension (.appex) --------------------------------------
# Built as a Foundation app extension: no main(), entry point is
# NSExtensionMain (the principal class comes from the appex Info.plist).
# -module-name must match the NSExtensionPrincipalClass module prefix.
echo "Building Fleet PSSO extension..."
mkdir -p "$APPEX_MACOS_DIR"
EXT_SOURCES=(
"$EXT_SRC_DIR/AuthenticationViewController.swift"
"$EXT_SRC_DIR/AuthenticationViewController+PSSO.swift"
"$EXT_SRC_DIR/AuthenticationViewController+Shared.swift"
"$EXT_SRC_DIR/AuthenticationViewController+Networking.swift"
)
EXT_SWIFT_FLAGS=(
-sdk "$SDK" -parse-as-library -O
-module-name FleetPSSOExtension
-framework AuthenticationServices -framework IOKit
-Xlinker -e -Xlinker _NSExtensionMain
)
swiftc -target arm64-apple-macos14 "${EXT_SWIFT_FLAGS[@]}" \
-o "$BUILD_DIR/FleetPSSOExtension-arm64" "${EXT_SOURCES[@]}"
swiftc -target x86_64-apple-macos14 "${EXT_SWIFT_FLAGS[@]}" \
-o "$BUILD_DIR/FleetPSSOExtension-x86_64" "${EXT_SOURCES[@]}"
lipo -create \
"$BUILD_DIR/FleetPSSOExtension-arm64" \
"$BUILD_DIR/FleetPSSOExtension-x86_64" \
-output "$APPEX_MACOS_DIR/FleetPSSOExtension"
rm "$BUILD_DIR/FleetPSSOExtension-arm64" "$BUILD_DIR/FleetPSSOExtension-x86_64"
cp "$EXT_SRC_DIR/Info.plist" "$APPEX_CONTENTS_DIR/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $EXT_BUNDLE_ID" "$APPEX_CONTENTS_DIR/Info.plist"
# Keep the embedded extension's version in lockstep with the host app.
APP_SHORT_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$CONTENTS_DIR/Info.plist")
APP_BUILD_VERSION=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$CONTENTS_DIR/Info.plist")
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $APP_SHORT_VERSION" "$APPEX_CONTENTS_DIR/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $APP_BUILD_VERSION" "$APPEX_CONTENTS_DIR/Info.plist"
# --- Optional code signing (local development) ------------------------------
# The associated-domains entitlements are Apple-managed; codesign only honors
# them with a matching provisioning profile embedded in the bundle. This mirrors
# CI's inside-out signing (extension first, then host app), but with the dev
# team's identity, bundle IDs, and profiles.
if [ -n "$SIGNING_IDENTITY" ]; then
echo "Signing under team $TEAM_ID with identity: $SIGNING_IDENTITY"
if [ -z "$APP_PROFILE" ] || [ -z "$EXT_PROFILE" ]; then
echo "ERROR: SIGNING_IDENTITY is set but APP_PROFILE / EXT_PROFILE are not." >&2
echo "Signing without an embedded profile leaves the restricted associated-domains" >&2
echo "entitlements unauthorized, so Platform SSO will not engage." >&2
exit 1
fi
# Substitute the dev team + bundle IDs into throwaway copies of the committed
# entitlements so the sealed application-identifier matches the signed binary.
APP_ENT="$BUILD_DIR/FleetDesktop.dev.entitlements"
EXT_ENT="$BUILD_DIR/FleetPSSOExtension.dev.entitlements"
cp "$SRC_DIR/FleetDesktop.entitlements" "$APP_ENT"
cp "$EXT_SRC_DIR/FleetPSSOExtension.entitlements" "$EXT_ENT"
/usr/libexec/PlistBuddy -c "Set :com.apple.application-identifier $TEAM_ID.$APP_BUNDLE_ID" "$APP_ENT"
/usr/libexec/PlistBuddy -c "Set :com.apple.developer.team-identifier $TEAM_ID" "$APP_ENT"
/usr/libexec/PlistBuddy -c "Set :com.apple.application-identifier $TEAM_ID.$EXT_BUNDLE_ID" "$EXT_ENT"
/usr/libexec/PlistBuddy -c "Set :com.apple.developer.team-identifier $TEAM_ID" "$EXT_ENT"
cp "$EXT_PROFILE" "$APPEX_CONTENTS_DIR/embedded.provisionprofile"
cp "$APP_PROFILE" "$CONTENTS_DIR/embedded.provisionprofile"
codesign --force --options runtime --sign "$SIGNING_IDENTITY" \
--entitlements "$EXT_ENT" "$APPEX_DIR"
codesign --force --options runtime --sign "$SIGNING_IDENTITY" \
--entitlements "$APP_ENT" "$APP_DIR"
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
fi
echo "Build complete: $APP_DIR"
echo " embedded extension: $APPEX_DIR"
echo "Run with: open \"$APP_DIR\""