macOS Update Management
Implement enterprise-grade macOS update management across your MacFleet deployment with automated patch deployment, compliance monitoring, staged rollouts, and comprehensive update policies. This tutorial provides solutions for maintaining security patches and OS currency while minimizing disruption to business operations.
Understanding macOS Update Management
macOS provides several methods for managing operating system updates programmatically:
- Software Update utility - Command-line tool for checking and installing updates
- startosinstall - Direct OS installation from installer applications
- MDM Update Commands - Mobile Device Management integration
- Configuration Profiles - Policy-based update control
- Apple Software Lookup Service - Update availability checking
Basic Update Operations
List Available Updates
#!/bin/bash
# List all available OS versions for installation
softwareupdate --list-full-installers | grep 'macOS' | awk '{print ++count " " $0}'
Download OS Installer
#!/bin/bash
# Fetch specific OS version installer
softwareupdate --fetch-full-installer --full-installer-version 12.1
echo "OS installer downloaded successfully"
Install OS Update
#!/bin/bash
# Basic OS update installation
osVersion="$1"
majorVersion=$(echo $osVersion | cut -d "." -f 1)
minorVersion=$(echo $osVersion | cut -d "." -f 2)
if [ $majorVersion == "12" ]; then
installerPath="Install macOS Monterey.app"
elif [ $majorVersion == "11" ]; then
installerPath="Install macOS Big Sur.app"
elif [ $minorVersion == "15"* ]; then
installerPath="Install macOS Catalina.app"
fi
fullPath="/Applications/$installerPath/Contents/Resources/startosinstall"
softwareupdate --fetch-full-installer --full-installer-version $osVersion
echo <Password> | "$fullPath" --agreetolicense --forcequitapps --nointeraction --user <Username> --stdinpass
Enterprise Update Management System
Comprehensive Update Management Tool
#!/bin/bash
# MacFleet Enterprise macOS Update Management Tool
# Automated patch deployment and compliance monitoring
# Configuration
CONFIG_FILE="/etc/macfleet/update_policy.conf"
LOG_FILE="/var/log/macfleet_updates.log"
CACHE_DIR="/Library/MacFleet/Updates"
REPORT_DIR="/var/log/macfleet_reports"
# Create directories
mkdir -p "$(dirname "$CONFIG_FILE")" "$(dirname "$LOG_FILE")" "$CACHE_DIR" "$REPORT_DIR"
# Default update policy
cat > "$CONFIG_FILE" 2>/dev/null << 'EOF' || true
# MacFleet macOS Update Management Policy
# Version: 2.0
# Update Enforcement
ENFORCE_SECURITY_UPDATES=true
ALLOW_MAJOR_OS_UPDATES=false
AUTO_INSTALL_SECURITY_PATCHES=true
DEFER_MAJOR_UPDATES_DAYS=30
# Scheduling Configuration
UPDATE_CHECK_INTERVAL=86400 # 24 hours
MAINTENANCE_WINDOW_START="02:00"
MAINTENANCE_WINDOW_END="06:00"
WEEKEND_UPDATES_ALLOWED=true
BUSINESS_HOURS_UPDATES=false
# Deployment Strategy
STAGED_ROLLOUT=true
PILOT_GROUP_PERCENTAGE=10
PRODUCTION_DELAY_DAYS=7
EMERGENCY_PATCH_IMMEDIATE=true
# System Requirements
MIN_BATTERY_PERCENTAGE=50
MIN_FREE_SPACE_GB=20
REQUIRE_AC_POWER=true
MAX_REBOOT_ATTEMPTS=3
# Notification Settings
NOTIFY_USERS_BEFORE_UPDATE=true
NOTIFICATION_LEAD_TIME_HOURS=24
FORCE_UPDATE_AFTER_DEFERRALS=5
SEND_COMPLETION_REPORTS=true
# Compatibility Settings
EXCLUDE_BETA_UPDATES=true
VALIDATE_APP_COMPATIBILITY=true
BACKUP_BEFORE_MAJOR_UPDATE=true
EOF
# Source configuration
source "$CONFIG_FILE" 2>/dev/null || true
# Logging function
log_action() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Get current macOS version
get_current_version() {
sw_vers -productVersion
}
# Get available updates
get_available_updates() {
echo "=== Available Updates ==="
# Check for software updates
local updates
updates=$(softwareupdate --list 2>/dev/null)
if echo "$updates" | grep -q "No new software available"; then
echo "✅ System is up to date"
return 0
else
echo "Available updates:"
echo "$updates"
return 1
fi
}
# Get available OS installers
get_available_installers() {
echo "=== Available OS Installers ==="
# List full installers
local installers
installers=$(softwareupdate --list-full-installers 2>/dev/null)
if [[ -n "$installers" ]]; then
echo "$installers" | grep 'macOS' | awk '{print ++count " " $0}'
else
echo "No OS installers available"
fi
}
# Check system readiness for updates
check_update_readiness() {
echo "=== Update Readiness Check ==="
local issues=0
local warnings=0
# Check battery level
local battery_level
if command -v pmset >/dev/null; then
battery_level=$(pmset -g batt | grep -Eo "\d+%" | tr -d '%' | head -1)
if [[ -n "$battery_level" && $battery_level -lt $MIN_BATTERY_PERCENTAGE ]]; then
echo "❌ Battery level too low: ${battery_level}% (min: ${MIN_BATTERY_PERCENTAGE}%)"
((issues++))
else
echo "✅ Battery level adequate: ${battery_level:-AC Power}%"
fi
fi
# Check AC power if required
if [[ "$REQUIRE_AC_POWER" == "true" ]]; then
local ac_power
ac_power=$(pmset -g ps | grep "AC Power" || echo "")
if [[ -z "$ac_power" ]]; then
echo "❌ AC power required but not connected"
((issues++))
else
echo "✅ AC power connected"
fi
fi
# Check free disk space
local free_space_gb
free_space_gb=$(df / | awk 'NR==2 {print int($4/1024/1024)}')
if [[ $free_space_gb -lt $MIN_FREE_SPACE_GB ]]; then
echo "❌ Insufficient disk space: ${free_space_gb}GB (min: ${MIN_FREE_SPACE_GB}GB)"
((issues++))
else
echo "✅ Sufficient disk space: ${free_space_gb}GB"
fi
# Check if system is in maintenance window
local current_time
current_time=$(date '+%H:%M')
if [[ "$current_time" > "$MAINTENANCE_WINDOW_START" && "$current_time" < "$MAINTENANCE_WINDOW_END" ]]; then
echo "✅ Within maintenance window"
else
echo "⚠️ Outside maintenance window (current: $current_time)"
((warnings++))
fi
# Check if weekend updates are allowed
local day_of_week
day_of_week=$(date '+%u') # 1=Monday, 7=Sunday
if [[ $day_of_week -gt 5 ]] && [[ "$WEEKEND_UPDATES_ALLOWED" != "true" ]]; then
echo "❌ Weekend updates not allowed"
((issues++))
fi
echo "Readiness summary: $issues issues, $warnings warnings"
return $issues
}
# Install security updates
install_security_updates() {
echo "=== Installing Security Updates ==="
if [[ "$AUTO_INSTALL_SECURITY_PATCHES" != "true" ]]; then
echo "Security update auto-installation disabled"
return 0
fi
# Check readiness
if ! check_update_readiness >/dev/null; then
echo "❌ System not ready for updates"
return 1
fi
# Install recommended updates
echo "Installing security and recommended updates..."
log_action "Starting security update installation"
# Use softwareupdate to install recommended updates
if softwareupdate --install --recommended --verbose; then
echo "✅ Security updates installed successfully"
log_action "Security updates installed successfully"
# Check if restart is required
if softwareupdate --list | grep -q "restart"; then
echo "⚠️ System restart required"
log_action "System restart required after security updates"
fi
return 0
else
echo "❌ Failed to install security updates"
log_action "FAILED: Security update installation"
return 1
fi
}
# Download OS installer
download_os_installer() {
local target_version="$1"
echo "=== Downloading OS Installer ==="
echo "Target version: $target_version"
# Validate version format
if [[ ! "$target_version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
echo "❌ Invalid version format: $target_version"
return 1
fi
# Download installer
echo "Downloading macOS $target_version installer..."
log_action "Downloading macOS installer: $target_version"
if softwareupdate --fetch-full-installer --full-installer-version "$target_version"; then
echo "✅ macOS installer downloaded successfully"
log_action "macOS installer downloaded: $target_version"
# Verify installer exists
local installer_path
installer_path=$(find /Applications -name "Install macOS*.app" -path "*$target_version*" 2>/dev/null | head -1)
if [[ -n "$installer_path" && -d "$installer_path" ]]; then
echo "Installer location: $installer_path"
return 0
else
# Check for installer by major version
local major_version
major_version=$(echo "$target_version" | cut -d'.' -f1)
case "$major_version" in
"15")
installer_path="/Applications/Install macOS Sequoia.app"
;;
"14")
installer_path="/Applications/Install macOS Sonoma.app"
;;
"13")
installer_path="/Applications/Install macOS Ventura.app"
;;
"12")
installer_path="/Applications/Install macOS Monterey.app"
;;
"11")
installer_path="/Applications/Install macOS Big Sur.app"
;;
*)
echo "❌ Unknown macOS version: $target_version"
return 1
;;
esac
if [[ -d "$installer_path" ]]; then
echo "Installer found: $installer_path"
return 0
else
echo "❌ Installer not found after download"
return 1
fi
fi
else
echo "❌ Failed to download macOS installer"
log_action "FAILED: macOS installer download for $target_version"
return 1
fi
}
# Install major OS update
install_major_update() {
local target_version="$1"
local admin_user="$2"
local admin_password="$3"
echo "=== Installing Major OS Update ==="
echo "Target version: $target_version"
echo "Admin user: $admin_user"
if [[ "$ALLOW_MAJOR_OS_UPDATES" != "true" ]]; then
echo "❌ Major OS updates not allowed by policy"
return 1
fi
# Check readiness
if ! check_update_readiness; then
echo "❌ System not ready for major update"
return 1
fi
# Find installer
local installer_path
local major_version
major_version=$(echo "$target_version" | cut -d'.' -f1)
case "$major_version" in
"15")
installer_path="/Applications/Install macOS Sequoia.app"
;;
"14")
installer_path="/Applications/Install macOS Sonoma.app"
;;
"13")
installer_path="/Applications/Install macOS Ventura.app"
;;
"12")
installer_path="/Applications/Install macOS Monterey.app"
;;
"11")
installer_path="/Applications/Install macOS Big Sur.app"
;;
*)
echo "❌ Unsupported macOS version: $target_version"
return 1
;;
esac
if [[ ! -d "$installer_path" ]]; then
echo "Installer not found, downloading..."
if ! download_os_installer "$target_version"; then
return 1
fi
fi
# Prepare installation command
local install_cmd="$installer_path/Contents/Resources/startosinstall"
local install_args="--agreetolicense --forcequitapps --nointeraction"
# Check if Apple Silicon Mac (requires credentials)
if system_profiler SPHardwareDataType | grep -q "Apple M"; then
if [[ -z "$admin_user" || -z "$admin_password" ]]; then
echo "❌ Apple Silicon Mac requires admin credentials"
return 1
fi
install_args="$install_args --user $admin_user --stdinpass"
fi
echo "Starting macOS installation..."
log_action "Starting major OS update to $target_version"
# Execute installation
if [[ -n "$admin_password" ]]; then
echo "$admin_password" | "$install_cmd" $install_args
else
"$install_cmd" $install_args
fi
local result=$?
if [[ $result -eq 0 ]]; then
echo "✅ macOS update initiated successfully"
log_action "macOS update initiated successfully to $target_version"
echo "⚠️ System will restart to complete installation"
else
echo "❌ Failed to initiate macOS update (exit code: $result)"
log_action "FAILED: macOS update initiation to $target_version (exit code: $result)"
fi
return $result
}
# Generate update compliance report
generate_update_report() {
local report_file="$REPORT_DIR/update_compliance_$(date +%Y%m%d_%H%M%S).json"
echo "=== Generating Update Compliance Report ==="
# Get current system information
local current_version
current_version=$(sw_vers -productVersion)
local build_version
build_version=$(sw_vers -buildVersion)
local hardware_model
hardware_model=$(system_profiler SPHardwareDataType | grep "Model Identifier" | awk -F: '{print $2}' | xargs)
# Check for available updates
local updates_available="false"
local security_updates_available="false"
if ! softwareupdate --list 2>/dev/null | grep -q "No new software available"; then
updates_available="true"
if softwareupdate --list 2>/dev/null | grep -i "security\|recommended"; then
security_updates_available="true"
fi
fi
# Calculate days since last update
local last_update_date
last_update_date=$(system_profiler SPInstallHistoryDataType | grep "Install Date" | head -1 | awk -F: '{print $2}' | xargs)
local days_since_update="unknown"
if [[ -n "$last_update_date" ]]; then
local last_update_epoch
last_update_epoch=$(date -j -f "%m/%d/%y" "$last_update_date" "+%s" 2>/dev/null || echo "0")
local current_epoch
current_epoch=$(date "+%s")
days_since_update=$(( (current_epoch - last_update_epoch) / 86400 ))
fi
# Create JSON report
cat > "$report_file" << EOF
{
"report_type": "update_compliance",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"device_info": {
"hostname": "$(hostname)",
"serial_number": "$(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F: '{print $2}' | xargs)",
"hardware_model": "$hardware_model",
"current_os_version": "$current_version",
"build_version": "$build_version"
},
"update_status": {
"updates_available": $updates_available,
"security_updates_available": $security_updates_available,
"days_since_last_update": $days_since_update,
"last_update_date": "$last_update_date",
"auto_update_enabled": $(defaults read /Library/Preferences/com.apple.SoftwareUpdate AutomaticCheckEnabled 2>/dev/null || echo false)
},
"policy_compliance": {
"security_updates_enforced": $ENFORCE_SECURITY_UPDATES,
"major_updates_allowed": $ALLOW_MAJOR_OS_UPDATES,
"auto_install_enabled": $AUTO_INSTALL_SECURITY_PATCHES,
"staged_rollout_enabled": $STAGED_ROLLOUT
},
"system_readiness": {
"sufficient_battery": true,
"ac_power_connected": $(pmset -g ps | grep -q "AC Power" && echo true || echo false),
"sufficient_disk_space": true,
"in_maintenance_window": false
}
}
EOF
echo "Update compliance report saved to: $report_file"
log_action "Update compliance report generated: $report_file"
}
# Main function with argument handling
main() {
log_action "=== MacFleet Update Management Tool Started ==="
case "${1:-status}" in
"check")
get_available_updates
;;
"security")
install_security_updates
;;
"download")
download_os_installer "$2"
;;
"install")
install_major_update "$2" "$3" "$4"
;;
"readiness")
check_update_readiness
;;
"installers")
get_available_installers
;;
"report")
generate_update_report
;;
"status"|*)
echo "Current macOS version: $(get_current_version)"
get_available_updates
;;
esac
log_action "=== Update management operation completed ==="
}
# Execute main function
main "$@"
Advanced Update Management
Staged Rollout Management
#!/bin/bash
# Staged rollout management for enterprise deployments
manage_staged_rollout() {
local update_version="$1"
local deployment_stage="${2:-pilot}"
echo "=== Staged Rollout Management ==="
echo "Update version: $update_version"
echo "Deployment stage: $deployment_stage"
# Define device groups
local device_serial
device_serial=$(system_profiler SPHardwareDataType | grep "Serial Number" | awk -F: '{print $2}' | xargs)
# Calculate hash for consistent group assignment
local hash_value
hash_value=$(echo "$device_serial" | md5 | cut -c1-2)
local numeric_hash
numeric_hash=$(printf "%d" "0x$hash_value")
local group_percentage
group_percentage=$((numeric_hash % 100))
case "$deployment_stage" in
"pilot")
if [[ $group_percentage -lt $PILOT_GROUP_PERCENTAGE ]]; then
echo "✅ Device selected for pilot deployment"
log_action "Device included in pilot group for $update_version"
return 0
else
echo "⏳ Device not in pilot group, waiting for production rollout"
return 1
fi
;;
"production")
echo "✅ Device eligible for production deployment"
log_action "Device included in production rollout for $update_version"
return 0
;;
"emergency")
echo "🚨 Emergency deployment - bypassing staging"
log_action "Emergency deployment initiated for $update_version"
return 0
;;
*)
echo "❌ Unknown deployment stage: $deployment_stage"
return 1
;;
esac
}
# Usage example:
# manage_staged_rollout "14.2.1" "pilot"
Update Compatibility Checking
#!/bin/bash
# Check application compatibility before major updates
check_app_compatibility() {
local target_version="$1"
echo "=== Application Compatibility Check ==="
if [[ "$VALIDATE_APP_COMPATIBILITY" != "true" ]]; then
echo "App compatibility checking disabled"
return 0
fi
local incompatible_apps=()
# Check for 32-bit applications (not supported in macOS 10.15+)
local target_major
target_major=$(echo "$target_version" | cut -d'.' -f1)
if [[ $target_major -ge 10 ]]; then
echo "Checking for 32-bit applications..."
# Use system_profiler to find 32-bit apps
local bit32_apps
bit32_apps=$(system_profiler SPApplicationsDataType | grep -B1 -A1 "64-Bit (Intel): No" | grep "Location:" | awk -F: '{print $2}' | xargs)
if [[ -n "$bit32_apps" ]]; then
echo "⚠️ Found 32-bit applications:"
echo "$bit32_apps"
incompatible_apps+=("32-bit applications")
fi
fi
# Check for known incompatible applications
local known_incompatible=(
"/Applications/Adobe CS6"
"/Applications/Microsoft Office 2011"
"/Applications/Final Cut Pro 7"
)
for app_path in "${known_incompatible[@]}"; do
if [[ -d "$app_path" ]]; then
echo "⚠️ Found known incompatible app: $app_path"
incompatible_apps+=("$(basename "$app_path")")
fi
done
# Generate compatibility report
if [[ ${#incompatible_apps[@]} -eq 0 ]]; then
echo "✅ No compatibility issues detected"
return 0
else
echo "❌ Compatibility issues found:"
printf ' - %s\n' "${incompatible_apps[@]}"
# Log compatibility issues
log_action "App compatibility issues found for $target_version: ${incompatible_apps[*]}"
return 1
fi
}
check_app_compatibility "14.0"
Automated Backup Before Updates
#!/bin/bash
# Create system backup before major updates
create_pre_update_backup() {
local backup_destination="${1:-/Volumes/Backup}"
echo "=== Pre-Update Backup ==="
if [[ "$BACKUP_BEFORE_MAJOR_UPDATE" != "true" ]]; then
echo "Pre-update backup disabled"
return 0
fi
# Check backup destination
if [[ ! -d "$backup_destination" ]]; then
echo "❌ Backup destination not available: $backup_destination"
return 1
fi
# Create Time Machine backup
echo "Creating Time Machine backup..."
if tmutil startbackup --auto --block; then
echo "✅ Time Machine backup completed"
log_action "Pre-update Time Machine backup completed"
else
echo "⚠️ Time Machine backup failed, continuing with update"
log_action "WARNING: Pre-update Time Machine backup failed"
fi
# Create system configuration backup
local config_backup="$backup_destination/macfleet_config_$(date +%Y%m%d_%H%M%S).tar.gz"
echo "Creating configuration backup..."
if tar -czf "$config_backup" \
/etc/macfleet \
/Library/Preferences \
/System/Library/LaunchDaemons/com.macfleet.* \
2>/dev/null; then
echo "✅ Configuration backup created: $config_backup"
log_action "Configuration backup created: $config_backup"
else
echo "⚠️ Configuration backup failed"
log_action "WARNING: Configuration backup failed"
fi
return 0
}
create_pre_update_backup
Monitoring and Compliance
Update Compliance Monitoring
#!/bin/bash
# Monitor update compliance across fleet
monitor_update_compliance() {
echo "=== Update Compliance Monitoring ==="
local current_version
current_version=$(sw_vers -productVersion)
# Check against security baselines
local security_baseline="14.2.1" # Example baseline
if [[ "$(printf '%s\n' "$security_baseline" "$current_version" | sort -V | head -1)" != "$security_baseline" ]]; then
echo "❌ Below security baseline (current: $current_version, required: $security_baseline)"
log_action "COMPLIANCE VIOLATION: Below security baseline $security_baseline"
# Trigger remediation if enabled
if [[ "$ENFORCE_SECURITY_UPDATES" == "true" ]]; then
echo "Initiating automatic remediation..."
install_security_updates
fi
else
echo "✅ Meets security baseline requirements"
fi
# Check for overdue updates
local last_check
last_check=$(defaults read /Library/Preferences/com.apple.SoftwareUpdate LastSuccessfulDate 2>/dev/null || echo "")
if [[ -n "$last_check" ]]; then
local days_since_check
days_since_check=$(( ($(date +%s) - $(date -j -f "%Y-%m-%d %H:%M:%S %z" "$last_check" +%s 2>/dev/null || echo 0)) / 86400 ))
if [[ $days_since_check -gt 7 ]]; then
echo "⚠️ Updates not checked for $days_since_check days"
log_action "WARNING: Updates not checked for $days_since_check days"
fi
fi
}
monitor_update_compliance
Emergency Patch Deployment
#!/bin/bash
# Emergency patch deployment for critical security updates
deploy_emergency_patch() {
local patch_version="$1"
local override_policies="${2:-false}"
echo "=== Emergency Patch Deployment ==="
echo "Patch version: $patch_version"
echo "Override policies: $override_policies"
log_action "EMERGENCY: Deploying critical patch $patch_version"
# Override normal restrictions for emergency patches
if [[ "$override_policies" == "true" ]]; then
# Temporarily modify configuration
local original_config
original_config=$(cat "$CONFIG_FILE")
# Emergency configuration
cat > "$CONFIG_FILE" << 'EOF'
ENFORCE_SECURITY_UPDATES=true
AUTO_INSTALL_SECURITY_PATCHES=true
EMERGENCY_PATCH_IMMEDIATE=true
REQUIRE_AC_POWER=false
MIN_BATTERY_PERCENTAGE=20
BUSINESS_HOURS_UPDATES=true
EOF
# Install emergency patch
if install_security_updates; then
echo "✅ Emergency patch deployed successfully"
log_action "Emergency patch $patch_version deployed successfully"
else
echo "❌ Emergency patch deployment failed"
log_action "CRITICAL: Emergency patch $patch_version deployment FAILED"
fi
# Restore original configuration
echo "$original_config" > "$CONFIG_FILE"
else
# Standard emergency deployment
install_security_updates
fi
}
# Example usage:
# deploy_emergency_patch "14.2.1" "true"
Important Configuration Notes
macOS Update Tools
- softwareupdate - Command-line Software Update utility
- startosinstall - Direct OS installation tool
- system_profiler - System information and update history
- tmutil - Time Machine backup utility
- pmset - Power management settings
Apple Silicon Considerations
- Admin credentials required - Updates need user authentication
- Secure Boot policies - May affect update installation
- Recovery mode options - Different from Intel Macs
- Reduced kernel extensions - System extension model
Best Practices for Enterprise
-
Staged Deployment Strategy
- Test updates on pilot group first
- Monitor for issues before full rollout
- Maintain rollback capabilities
-
Compliance Management
- Define security baselines
- Monitor update status regularly
- Automate compliance reporting
-
Risk Mitigation
- Backup before major updates
- Test application compatibility
- Plan for emergency patches
-
User Communication
- Notify users of scheduled updates
- Provide clear maintenance windows
- Offer deferral options within policy limits
Troubleshooting Common Issues
- Insufficient disk space - Clean up before updates or increase storage
- Power requirements - Ensure AC power for major updates
- Network connectivity - Verify access to Apple's update servers
- Authentication failures - Check admin credentials on Apple Silicon
- Installation failures - Review system logs and compatibility
Remember to test update procedures thoroughly in a non-production environment before deploying across your entire MacFleet.