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

Apple Script : macOS Automated Updates

I was looking to keep all our corporate Mac mini devices up-to-date by checking for updates weekly and if a update is found it is installed and if a reboot is required, it will automatically reboot at 8 PM that evening.

This means when you check the software update settings and it looks like below the script will take care of that update, just ensure automatic update is enabled:


If an update is found, but no reboot is required then the system is not rebooted because there’s no requirement for that reboot.

Script Flow

This is the process flow of the script:

  • Check for updates weekly at 8 PM
  • Automatically install and restart when needed
  • Allow manual triggering outside the schedule
  • Log all activity for monitoring

Step 1: Create the Main Update Script

sudo mkdir -p /usr/local/bin
sudo nano /usr/local/bin/automated_update.sh

You will then need this content inside that file:

#!/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 with automatic restart
# The --restart flag will only restart if updates actually require it
/usr/sbin/softwareupdate --install --all --restart

# Update the timestamp (this will only execute if the above command doesn't trigger a restart)
echo "$CURRENT_TIME" > "$LAST_UPDATE_FILE"
log_message "Updates completed successfully"

Save and exit (Ctrl+X, then Y, then Enter in nano).

Step 2: Make the Script Executable

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

Step 3: Create the Launch Daemon

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

This use this code in that file:

<?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 and exit.

Step 4: Set Correct Permissions for Launch Daemon

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

Step 5: Load the Launch Daemon

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

Step 6: Verify the Launch Daemon is Running

sudo launchctl list | grep com.softwareupdate.automated

You should see output like: - 0 com.softwareupdate.automated

Step 7: Create Manual Trigger Script (Optional)

I always like a method to manually trigger the update script for unplanned work or possible security incidents where an update needs to be installed outside the weekly check.

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

Then you need this code in that file:

#!/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..."

# Create the force update trigger file
sudo touch "$FORCE_UPDATE_FILE"

# Run the update script immediately
sudo "$UPDATE_SCRIPT"

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

Save and exit.

Step 8: Make Manual Trigger Script Executable

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

Step 9: Test the Setup

This will force a run of the manual script that should check for updates and output that content to a log file:

sudo /usr/local/bin/manual_update.sh

Check the log:

tail -f /var/log/automated_updates.log

That should then look like this (no update in this example)

Wed Jul 16 08:15:51 BST 2025: Automated update check started
Wed Jul 16 08:15:51 BST 2025: Manual update trigger found
Wed Jul 16 08:15:51 BST 2025: Manual update triggered, checking for updates...
Wed Jul 16 08:15:57 BST 2025: No updates available

Monitoring automatic updates

  • View logs: tail -f /var/log/automated_updates.log
  • Check if daemon is running: sudo launchctl list | grep automated
  • Test manually: sudo /usr/local/bin/manual_update.sh

Uninstall : Fallback to Manual

You have decided that you’d rather manually do the updates on all your devices, And you have decided to switch off the automation and go back to living with the dinosaurs in caves:
# Unload the daemon
sudo launchctl unload /Library/LaunchDaemons/com.softwareupdate.automated.plist

# Remove files
sudo rm /Library/LaunchDaemons/com.softwareupdate.automated.plist
sudo rm /usr/local/bin/automated_update.sh
sudo rm /usr/local/bin/manual_update.sh

# Clean up log and state files
sudo rm /var/log/automated_updates.log
sudo rm /var/log/automated_updates_stdout.log
sudo rm /var/log/automated_updates_stderr.log
sudo rm /var/tmp/last_software_update
sudo rm /var/tmp/force_software_update
Previous Post Next Post

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