Skip to content

Instantly share code, notes, and snippets.

@foozmeat
Last active October 5, 2016 21:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save foozmeat/64a1aa45b9b823451a86 to your computer and use it in GitHub Desktop.
Save foozmeat/64a1aa45b9b823451a86 to your computer and use it in GitHub Desktop.
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