Intelligently manage software updates on Mac OS X
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
""" | |
Created on 9-9-2015 by James Moore (@foozmeat) | |
Install software updates automatically and intelligently. | |
If you improve this script please send me patches. | |
To get started create a JSON file called settings.json using the following format | |
{ | |
"global": { | |
"ignore": [ ], | |
"reboot": { "unassisted": false } | |
}, | |
"hosts": { | |
"buildvm": { | |
"ignore": [ "Command Line Tools", "Xcode" ], | |
"reboot": { "unassisted": true, "unless_running": ["xcodebuild"] | |
} | |
}, | |
"ghidorah": { | |
"ignore": [ "Xcode" ] | |
} | |
} | |
} | |
Packages listed in `ignore` sections are substring matched. Pre-10.10 machines will need to run this | |
script with `sudo` even if the user can use sudo without a password. This script should be safe to run | |
from cron - it only produces output if something happens. | |
""" | |
from subprocess import check_output, STDOUT | |
import json | |
import sys | |
import plistlib | |
from time import sleep | |
# JSON keys | |
kIGNORE = 'ignore' | |
kGLOBAL = 'global' | |
kHOSTS = 'hosts' | |
kREBOOT = 'reboot' | |
kUNASSISTED = 'unassisted' | |
kUNLESSRUNNING = 'unless_running' | |
def value_for_key(dictionary, key): | |
value = None | |
try: | |
value = dictionary[key] | |
except KeyError: | |
pass | |
return value | |
# First request a refresh of available udpates | |
check_output(["/usr/sbin/softwareupdate", "-l"], stderr=STDOUT) | |
# Next read in the list of updates from the plist | |
plist_data = check_output(('/usr/libexec/PlistBuddy', '-x', '/Library/Preferences/com.apple.SoftwareUpdate.plist', '-c', 'Print :RecommendedUpdates')) | |
package_list = plistlib.readPlistFromString(plist_data) | |
if len(package_list) == 0: | |
sys.exit(0) | |
with open('settings.json') as data_file: | |
settings = json.load(data_file) | |
hostname = check_output(['hostname']).lower().strip() | |
host_key = hostname | |
print "Host: %s" % hostname | |
# merge settings | |
global_settings = settings.get(kGLOBAL, {}) | |
hosts_settings = settings.get(kHOSTS, {}) | |
for h in hosts_settings.keys(): | |
if h in hostname: | |
host_key = h | |
host_settings = hosts_settings.get(host_key, {}) | |
ignores = set(global_settings.get(kIGNORE, [])) | |
ignores = ignores.union(set(host_settings.get(kIGNORE, []))) | |
print "Ignoring packages: %s" % ', '.join(ignores) | |
global_reboot_settings = global_settings.get(kREBOOT, {}) | |
host_reboot_settings = host_settings.get(kREBOOT, {}) | |
reboot_unassisted = global_reboot_settings.get(kUNASSISTED, False) | |
if kUNASSISTED in host_reboot_settings: | |
reboot_unassisted = host_reboot_settings.get(kUNASSISTED, False) | |
reboot_unless_running = set(global_reboot_settings.get(kUNLESSRUNNING, [])) | |
reboot_unless_running = reboot_unless_running.union(set(host_reboot_settings.get(kUNLESSRUNNING, []))) | |
reboot_unless_running = [x.lower() for x in reboot_unless_running] | |
print "Reboot unassisted: %s" % reboot_unassisted | |
if reboot_unassisted: | |
print "Unless running: %s" % ', '.join(reboot_unless_running) | |
restart_required = False | |
for package in package_list: | |
ignore_item = False | |
package_display_name = package["Display Name"] | |
package_version = package["Display Version"] | |
package_id = package["Identifier"] | |
package_name = '-'.join([package_id, package_version]) | |
print "Found package %s\n" % package_display_name | |
for i in ignores: | |
if i in package_name: | |
print "\tIgnoring\n" | |
ignore_item = True | |
if ignore_item: | |
print check_output(['/usr/sbin/softwareupdate', '--ignore', package_id], stderr=STDOUT) | |
continue | |
else: | |
print "\tInstalling\n" | |
install_output = check_output(['/usr/sbin/softwareupdate', '-i', package_name], stderr=STDOUT) | |
if 'restart' in install_output: | |
restart_required = True | |
print "\tRestart Required\n" | |
print "Complete" | |
if restart_required: | |
if not reboot_unassisted: | |
print "REBOOT REQUIRED" | |
else: | |
can_reboot = False | |
while not can_reboot: | |
process_list = check_output(['ps', '-A', '-o', 'command']).splitlines() | |
process_list = [x.lower() for x in process_list] | |
can_reboot = True | |
for i in reboot_unless_running: | |
for p in process_list: | |
if i in p: | |
print "Found %s, waiting to reboot" % i | |
can_reboot = False | |
sleep(30) | |
print "REBOOTING NOW" | |
check_output(['sudo', 'reboot'], stderr=STDOUT) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment