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
# 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