Last active
October 5, 2016 21:04
-
-
Save foozmeat/64a1aa45b9b823451a86 to your computer and use it in GitHub Desktop.
Intelligently manage software updates on Mac OS X
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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