<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #50056 ## Summary macOS FMA install scripts never checked the exit code of the install command (`installer -pkg` / `cp -R`) — the script's last statement is always `relaunch_application`, which exits 0 — so a failed install exited 0 and Fleet reported it installed. **Generated scripts.** The generator now propagates failure: both `installer -pkg` variants end with `|| exit $?`, and the `cp -R` path exits non-zero on a failed copy, removes the partial copy (so a failed fresh install isn't inventoried as the new version), and restores the app it moved aside. Regenerated `outputs/` for non-frozen generated apps are produced by the `ingest-maintained-apps` job, so they aren't committed here. **Custom scripts.** 9 of the 18 custom input scripts had the same bug and are fixed with the same pattern: Google Chrome, Zoom, Microsoft Edge, GitHub Desktop, Webex, Cycling '74 Max, Pd, Grammarly Desktop, and P4V. The DMG-based ones also now fail before removing/moving the existing app when the mount or staging copy fails, so a bad download can't leave a host with nothing. Their `outputs/*/darwin.json` are updated in the same commit (script content + recomputed 8-char sha256 ref, versions untouched), following the precedent of #49033. Docker Desktop (`set -euo pipefail`), 1Password/Slack/LogiTune (installer is the last statement), and the rest already propagated errors. **Frozen apps.** The ingest job never rewrites frozen outputs, so the 10 frozen apps with generated scripts (adobe-acrobat-pro, comet, evernote, firealpaca, keeper-password-manager, nvidia-geforce-now, pritunl, vnc-viewer, wins, worksheet-crafter) had the fix applied directly to their published `darwin.json` scripts — the exact text the current generator would emit, with pinned versions/URLs/hashes untouched. The 11th frozen app (logi-options+) uses a custom script that was already correct and in sync. # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/` (`changes/50056-fma-install-scripts-ignore-errors`). - [x] Untrusted data interpolated into shell scripts is validated against shell metacharacters. (No new untrusted interpolation: the guard reuses the same curated cask-derived name the adjacent lines already interpolate.) ## Testing - [x] Added/updated automated tests (three generator tests: pkg, pkg-with-choices, cp-R restore — the last now pins the exact emitted block). - [x] All 19 updated output manifests validated: embedded scripts pass `bash -n`, refs match `sha256(script)[:8]`, refs map stays key-sorted like Go's encoder. - [x] QA'd all new/changed functionality manually. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * macOS Fleet-maintained app installations now fail fast when installers, DMG extraction/mounting, or app copy steps error. * If an upgrade fails, the system removes any partial app and restores the previously installed version when available. * Improved robustness during app staging/copying, including safer handling of paths with spaces or special characters. * **Tests** * Added unit coverage to verify installer failure propagation and rollback behavior. * **Documentation** * Clarified that the install-script error handling applies to both generated and custom scripts, including already-published frozen apps. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Allen Houchins <allenhouchins@mac.com>
59 lines
2.0 KiB
Bash
59 lines
2.0 KiB
Bash
#!/bin/bash
|
|
|
|
quit_application() {
|
|
local bundle_id="$1"
|
|
local timeout_duration=10
|
|
local app_running; app_running=$(osascript -e "application id \"$bundle_id\" is running" 2>/dev/null)
|
|
if [[ "$app_running" != "true" ]]; then return; fi
|
|
local console_user; console_user=$(stat -f "%Su" /dev/console)
|
|
if [[ $EUID -eq 0 && "$console_user" == "root" ]]; then echo "Skipping quit for '$bundle_id'."; return; fi
|
|
echo "Quitting '$bundle_id'..."
|
|
SECONDS=0
|
|
while (( SECONDS < timeout_duration )); do
|
|
osascript -e "tell application id \"$bundle_id\" to quit" >/dev/null 2>&1 || true
|
|
if ! pgrep -f "$bundle_id" >/dev/null 2>&1; then echo "'$bundle_id' quit successfully."; return; fi
|
|
sleep 1
|
|
done
|
|
echo "'$bundle_id' did not quit."
|
|
}
|
|
|
|
[[ -n "$INSTALLER_PATH" && -f "$INSTALLER_PATH" ]] || { echo "missing installer"; exit 1; }
|
|
|
|
APPDIR="/Applications"
|
|
|
|
quit_application "com.perforce.p4v"
|
|
quit_application "com.perforce.p4merge"
|
|
quit_application "com.perforce.p4admin"
|
|
|
|
MOUNT_POINT="$(hdiutil attach -nobrowse -readonly "$INSTALLER_PATH" | awk '/\/Volumes\//{print $3; exit}')"
|
|
[[ -n "$MOUNT_POINT" ]] || { echo "failed to mount dmg"; exit 1; }
|
|
|
|
for app in p4v.app p4merge.app p4admin.app; do
|
|
if [[ -d "$MOUNT_POINT/$app" ]]; then
|
|
rm -rf "$APPDIR/$app" >/dev/null 2>&1 || true
|
|
if ! ditto "$MOUNT_POINT/$app" "$APPDIR/$app"; then
|
|
# remove the partial copy so a failed install isn't inventoried as installed
|
|
rm -rf "$APPDIR/$app" >/dev/null 2>&1 || true
|
|
hdiutil detach "$MOUNT_POINT" >/dev/null 2>&1 || true
|
|
echo "failed to install $app"
|
|
exit 1
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Install p4vc command line binary to /usr/local/bin
|
|
if [[ -f "$MOUNT_POINT/p4vc" ]]; then
|
|
mkdir -p /usr/local/bin
|
|
if ! cp "$MOUNT_POINT/p4vc" /usr/local/bin/p4vc; then
|
|
hdiutil detach "$MOUNT_POINT" >/dev/null 2>&1 || true
|
|
echo "failed to install p4vc"
|
|
exit 1
|
|
fi
|
|
chmod +x /usr/local/bin/p4vc
|
|
chown root:wheel /usr/local/bin/p4vc
|
|
fi
|
|
|
|
hdiutil detach "$MOUNT_POINT" >/dev/null 2>&1 || true
|
|
|
|
echo "p4v installed"
|