Notice: Due to size constraints and loading performance considerations, scripts referenced in blog posts are not attached directly. To request access, please complete the following form: Script Request Form Note: A Google account is required to access the form.
Disclaimer: I do not accept responsibility for any issues arising from scripts being run without adequate understanding. It is the user's responsibility to review and assess any code before execution. More information

Automating macOS Software Updates : Launch Daemon


If you manage multiple MacOS systems or simply want to ensure your Mac stays updated without manual intervention, automating the macOS software update process can save significant time and reduce security risks. This guide shows you how to create a fully automated patching system that handles everything from checking for updates to installing them and rebooting when necessary.

Why the Automation?

This automated solution eliminates the need for manual software update management by:

  • Automatically checking for macOS updates weekly at a scheduled time (8 PM by default)
  • Installing all available updates without user interaction including system updates, security patches, and app updates
  • Forcing automatic reboots when updates require them - no more waiting for manual confirmation
  • Providing manual trigger capability for immediate updates outside the regular schedule
  • Comprehensive logging of all update activities for monitoring and troubleshooting
  • Smart timing logic that prevents unnecessary update checks while ensuring timely patching

The system uses native macOS tools including softwareupdate, launchctl, and standard bash scripting, making it reliable and compatible with all modern macOS versions (macOS 10.13.4 and newer).

Step 1: Create the Required Directory

First, I need to create the directory where the scripts will live:

sudo mkdir -p /usr/local/bin

Step 2: Create the Main Update Script

Lets now create the primary automation script that handles all update logic:

sudo nano /usr/local/bin/automated_update.sh

The script content is:

#!/bin/bash

# Configuration
LAST_UPDATE_FILE="/var/tmp/last_software_update"
FORCE_UPDATE_FILE="/var/tmp/force_software_update"
CURRENT_TIME=$(date +%s)
ONE_WEEK_SECONDS=604800  # 7 days in seconds
LOG_FILE="/var/log/automated_updates.log"

# Function to log messages
log_message() {
    echo "$(date): $1" >> "$LOG_FILE"
}

log_message "Automated update check started"

# Check for manual force trigger
FORCE_UPDATE=false
if [ -f "$FORCE_UPDATE_FILE" ]; then
    log_message "Manual update trigger found"
    FORCE_UPDATE=true
    rm "$FORCE_UPDATE_FILE"  # Remove the trigger file
fi

# Check if it's been one week since last update (unless manually forced)
if [ "$FORCE_UPDATE" = false ]; then
    if [ -f "$LAST_UPDATE_FILE" ]; then
        LAST_UPDATE=$(cat "$LAST_UPDATE_FILE")
        TIME_DIFF=$((CURRENT_TIME - LAST_UPDATE))
        
        if [ $TIME_DIFF -lt $ONE_WEEK_SECONDS ]; then
            log_message "Not yet time for update check. Last update was $(date -r $LAST_UPDATE)"
            exit 0
        fi
    fi
    log_message "One week has passed, checking for updates..."
else
    log_message "Manual update triggered, checking for updates..."
fi

# Check if updates are available first
UPDATE_LIST=$(/usr/sbin/softwareupdate --list 2>&1)
if echo "$UPDATE_LIST" | grep -q "No new software available"; then
    log_message "No updates available"
    # Update timestamp even if no updates (to reset the 1-week timer)
    echo "$CURRENT_TIME" > "$LAST_UPDATE_FILE"
    exit 0
fi

log_message "Updates found, proceeding with installation..."
log_message "Available updates: $UPDATE_LIST"

# Run software update and capture the result
log_message "Installing updates..."
/usr/sbin/softwareupdate --install --all --restart

# Check if the update command was successful (exit code 0 means updates were installed)
UPDATE_RESULT=$?
if [ $UPDATE_RESULT -eq 0 ]; then
    # Updates were installed successfully - force a restart to be safe
    log_message "Updates installed successfully - forcing restart in 15 seconds for safety"
    sleep 15
    echo "$CURRENT_TIME" > "$LAST_UPDATE_FILE"  # Update timestamp before restart
    /sbin/shutdown -r now
else
    log_message "Software update command failed or no updates were processed"
fi

Save the file using Ctrl+X, then Y, then Enter.

Step 3: Make the Main Script Executable

The script needs executable permissions to run:

sudo chmod +x /usr/local/bin/automated_update.sh

Step 4: Create the Manual Trigger Script

I'll create a convenience script for triggering updates manually outside the regular schedule:

sudo nano /usr/local/bin/manual_update.sh

The manual trigger script content is:

#!/bin/bash

# Manual Software Update Trigger
# This script creates a trigger file and immediately runs the update check

FORCE_UPDATE_FILE="/var/tmp/force_software_update"
UPDATE_SCRIPT="/usr/local/bin/automated_update.sh"

echo "Manually triggering software update check..."
echo "You will be prompted for your admin password once..."

# Create the force update trigger file and run the script with a single sudo call
sudo bash -c "
    touch '$FORCE_UPDATE_FILE'
    '$UPDATE_SCRIPT'
"

echo "Manual update check completed. Check /var/log/automated_updates.log for details."

Save the file using Ctrl+X, then Y, then Enter.

Step 5: Make the Manual Trigger Script Executable

sudo chmod +x /usr/local/bin/manual_update.sh

Step 6: Create the Launch Daemon for Scheduling

I need to create a Launch Daemon that will run the script daily at 8 PM:

sudo nano /Library/LaunchDaemons/com.softwareupdate.automated.plist

The Launch Daemon configuration is:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.softwareupdate.automated</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/automated_update.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>20</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>
    <key>RunAtLoad</key>
    <false/>
    <key>StandardOutPath</key>
    <string>/var/log/automated_updates_stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/automated_updates_stderr.log</string>
</dict>
</plist>

Save the file using Ctrl+X, then Y, then Enter.

Step 7: Set Proper Permissions for the Launch Daemon

sudo chown root:wheel /Library/LaunchDaemons/com.softwareupdate.automated.plist
sudo chmod 644 /Library/LaunchDaemons/com.softwareupdate.automated.plist

Step 8: Load and Start the Launch Daemon

sudo launchctl load /Library/LaunchDaemons/com.softwareupdate.automated.plist

Step 9: Verify the Launch Daemon is Running

I can confirm the daemon loaded successfully:

sudo launchctl list | grep com.softwareupdate.automated

The output should show: - 0 com.softwareupdate.automated

Step 10: Test the Complete Setup

To verify everything works correctly, I'll test the manual trigger:

Open one terminal to monitor logs:

tail -f /var/log/automated_updates.log

In another terminal, trigger a manual update:

sudo /usr/local/bin/manual_update.sh

Monitoring and Maintenance

The system creates several log files for monitoring:

  • /var/log/automated_updates.log - Primary activity log
  • /var/log/automated_updates_stdout.log - Standard output from the Launch Daemon
  • /var/log/automated_updates_stderr.log - Error output from the Launch Daemon

To monitor update activity in real-time:

tail -f /var/log/automated_updates.log

To check recent system reboots:

last reboot | head -5

Manual Update Triggering

When immediate updates are needed outside the regular schedule, I can trigger the process manually:

sudo /usr/local/bin/manual_update.sh

This bypasses the weekly timer and immediately checks for and installs any available updates.

Uninstalling the Update platform

If removal becomes necessary, the complete uninstallation process is:

# Stop and remove the Launch Daemon
sudo launchctl unload /Library/LaunchDaemons/com.softwareupdate.automated.plist
sudo rm /Library/LaunchDaemons/com.softwareupdate.automated.plist

# Remove the scripts
sudo rm /usr/local/bin/automated_update.sh
sudo rm /usr/local/bin/manual_update.sh

# Clean up logs and state files
sudo rm /var/log/automated_updates*.log
sudo rm /var/tmp/last_software_update
sudo rm /var/tmp/force_software_update

Conclusion

This automated macOS update system provides a robust, hands-off approach to system patching that ensures security updates are applied promptly while minimizing disruption to daily workflows. The combination of scheduled automation with manual override capabilities makes it suitable for both individual users and enterprise environments where consistent patching is critical for security and compliance.

Previous Post Next Post

نموذج الاتصال