mirror of
https://github.com/munki/munki.git
synced 2026-01-02 20:50:20 -06:00
git-svn-id: http://munki.googlecode.com/svn/trunk@865 a4e17f2e-e282-11dd-95e1-755cbddbdd66
691 lines
27 KiB
Python
Executable File
691 lines
27 KiB
Python
Executable File
#!/usr/bin/python
|
|
# encoding: utf-8
|
|
#
|
|
# Copyright 2009-2010 Greg Neagle.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the 'License');
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an 'AS IS' BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""
|
|
managedsoftwareupdate
|
|
"""
|
|
|
|
import grp
|
|
import optparse
|
|
import os
|
|
import stat
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
# Do not place any imports with ObjC bindings above this!
|
|
try:
|
|
from Foundation import NSDate
|
|
except:
|
|
# Python is missing ObjC bindings. Run external report script.
|
|
from munkilib import utils
|
|
print >> sys.stderr, 'Python is missing ObjC bindings.'
|
|
scriptdir = os.path.realpath(os.path.dirname(sys.argv[0]))
|
|
script = os.path.join(scriptdir, 'report_broken_client')
|
|
try:
|
|
utils.runExternalScript(script)
|
|
except utils.ScriptNotFoundError:
|
|
pass # script is not required, so pass
|
|
except utils.RunExternalScriptError, e:
|
|
print >> sys.stderr, str(e)
|
|
sys.exit(1)
|
|
|
|
from munkilib import munkicommon
|
|
from munkilib import updatecheck
|
|
from munkilib import installer
|
|
from munkilib import munkistatus
|
|
from munkilib import appleupdates
|
|
from munkilib import FoundationPlist
|
|
from munkilib import utils
|
|
|
|
|
|
def getIdleSeconds():
|
|
"""Returns the number of seconds since the last mouse or keyboard event."""
|
|
cmd = ['/usr/sbin/ioreg', '-c', 'IOHIDSystem', '-d', '4']
|
|
proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(output, unused_err) = proc.communicate()
|
|
ioreglines = str(output).splitlines()
|
|
for line in ioreglines:
|
|
if 'Idle' in line:
|
|
parts = line.split()
|
|
return int(int(parts[3])/1000000000)
|
|
|
|
|
|
def networkUp():
|
|
"""Determine if the network is up by looking for any non-loopback
|
|
internet network interfaces.
|
|
|
|
Returns:
|
|
Boolean. True if loopback is found (network is up), False otherwise.
|
|
"""
|
|
cmd = ['/sbin/ifconfig', '-a', 'inet']
|
|
proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
(output, unused_err) = proc.communicate()
|
|
lines = str(output).splitlines()
|
|
for line in lines:
|
|
if 'inet' in line:
|
|
parts = line.split()
|
|
addr = parts[1]
|
|
if not addr in ['127.0.0.1', '0.0.0.0.0']:
|
|
return True
|
|
return False
|
|
|
|
|
|
def clearLastNotifiedDate():
|
|
"""Clear the last date the user was notified of updates."""
|
|
try:
|
|
plist = FoundationPlist.readPlist(
|
|
munkicommon.MANAGED_INSTALLS_PLIST_PATH)
|
|
if plist:
|
|
if 'LastNotifiedDate' in plist:
|
|
cmd = ['/usr/bin/defaults', 'delete',
|
|
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
|
|
'LastNotifiedDate']
|
|
unused_retcode = subprocess.call(cmd)
|
|
except FoundationPlist.NSPropertyListSerializationException:
|
|
pass
|
|
|
|
|
|
def createDirsIfNeeded(dirlist):
|
|
"""Create any missing directories needed by the munki tools.
|
|
|
|
Args:
|
|
dirlist: a sequence of directories.
|
|
Returns:
|
|
Boolean. True if all directories existed or were created,
|
|
False otherwise.
|
|
"""
|
|
for directory in dirlist:
|
|
if not os.path.exists(directory):
|
|
try:
|
|
os.mkdir(directory)
|
|
except (OSError, IOError):
|
|
print >> sys.stderr, 'ERROR: Could not create %s' % directory
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def initMunkiDirs():
|
|
"""Figure out where data directories should be and create them if needed.
|
|
|
|
Returns:
|
|
Boolean. True if all data dirs existed or were created, False otherwise.
|
|
"""
|
|
managedinstallprefs = munkicommon.prefs()
|
|
ManagedInstallDir = managedinstallprefs['ManagedInstallDir']
|
|
manifestsdir = os.path.join(ManagedInstallDir, 'manifests')
|
|
catalogsdir = os.path.join(ManagedInstallDir, 'catalogs')
|
|
cachedir = os.path.join(ManagedInstallDir, 'Cache')
|
|
logdir = os.path.join(ManagedInstallDir, 'Logs')
|
|
|
|
if not createDirsIfNeeded([ManagedInstallDir, manifestsdir, catalogsdir,
|
|
cachedir, logdir]):
|
|
munkicommon.display_error('Could not create needed directories '
|
|
'in %s' % ManagedInstallDir)
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
|
|
def doInstallTasks(only_forced=False):
|
|
"""Perform our installation/removal tasks.
|
|
|
|
Args:
|
|
only_forced: Boolean. If True, only do forced installs/removals.
|
|
|
|
Returns:
|
|
Boolean. True if a restart is required, False otherwise.
|
|
"""
|
|
if not only_forced:
|
|
# first, clear the last notified date
|
|
# so we can get notified of new changes after this round
|
|
# of installs
|
|
clearLastNotifiedDate()
|
|
|
|
need_to_restart = False
|
|
# munki updates take priority over Apple Updates, because
|
|
# a munki install or (especially) removal could make a
|
|
# pending Apple update no longer necessary or even complicate
|
|
# or prevent the removal of another item.
|
|
# Therefore we only install Apple updates if there are no
|
|
# pending munki updates.
|
|
|
|
if munkiUpdatesAvailable():
|
|
# install munki updates
|
|
try:
|
|
need_to_restart = installer.run(only_forced=only_forced)
|
|
except:
|
|
munkicommon.display_error('Unexpected error in '
|
|
' munkilib.installer:')
|
|
munkicommon.display_error(traceback.format_exc())
|
|
munkicommon.savereport()
|
|
exit(-1)
|
|
|
|
# clear any Apple update info since it may no longer
|
|
# be relevant
|
|
if not only_forced:
|
|
appleupdates.clearAppleUpdateInfo()
|
|
elif ((munkicommon.pref('InstallAppleSoftwareUpdates') or
|
|
applesoftwareupdatesonly) and not only_forced):
|
|
# are we supposed to handle Apple Software Updates?
|
|
try:
|
|
need_to_restart = appleupdates.installAppleUpdates()
|
|
except:
|
|
munkicommon.display_error('Unexpected error in '
|
|
' installAppleUpdates:')
|
|
munkicommon.display_error(traceback.format_exc())
|
|
munkicommon.savereport()
|
|
exit(-1)
|
|
|
|
munkicommon.savereport()
|
|
return need_to_restart
|
|
|
|
|
|
def doRestart():
|
|
"""Handle the need for a restart."""
|
|
restartMessage = 'Software installed or removed requires a restart.'
|
|
munkicommon.log(restartMessage)
|
|
if munkicommon.munkistatusoutput:
|
|
munkistatus.hideStopButton()
|
|
munkistatus.message(restartMessage)
|
|
munkistatus.detail('')
|
|
munkistatus.percent(-1)
|
|
else:
|
|
munkicommon.display_info(restartMessage)
|
|
|
|
if not munkicommon.currentGUIusers():
|
|
# no-one is logged in and we're at the loginwindow
|
|
time.sleep(5)
|
|
unused_retcode = subprocess.call(['/sbin/shutdown', '-r', 'now'])
|
|
else:
|
|
if munkicommon.munkistatusoutput:
|
|
# someone is logged in and we're using munkistatus
|
|
munkicommon.display_info(
|
|
'Notifying currently logged-in user to restart.')
|
|
munkistatus.activate()
|
|
munkistatus.restartAlert()
|
|
munkicommon.osascript(
|
|
'tell application "System Events" to restart')
|
|
else:
|
|
print 'Please restart immediately.'
|
|
|
|
|
|
def munkiUpdatesAvailable():
|
|
"""Return True if there are available updates, False otherwise."""
|
|
updatesavailable = False
|
|
installinfo = os.path.join(munkicommon.pref('ManagedInstallDir'),
|
|
'InstallInfo.plist')
|
|
if os.path.exists(installinfo):
|
|
try:
|
|
plist = FoundationPlist.readPlist(installinfo)
|
|
updatesavailable = len(plist.get('removals', [])) or \
|
|
len(plist.get('managed_installs', []))
|
|
except (AttributeError,
|
|
FoundationPlist.NSPropertyListSerializationException):
|
|
munkicommon.display_error('Install info at %s is invalid.' %
|
|
installinfo)
|
|
return updatesavailable
|
|
|
|
|
|
def recordUpdateCheckResult(result):
|
|
"""Record last check date and result"""
|
|
now = NSDate.new()
|
|
cmd = ['/usr/bin/defaults', 'write',
|
|
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
|
|
'LastCheckDate', '-date', str(now)]
|
|
unused_retcode = subprocess.call(cmd)
|
|
cmd = ['/usr/bin/defaults', 'write',
|
|
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
|
|
'LastCheckResult', '-int', str(result)]
|
|
unused_retcode = subprocess.call(cmd)
|
|
|
|
|
|
def notifyUserOfUpdates():
|
|
"""Notify the logged-in user of available updates.
|
|
|
|
Returns:
|
|
Boolean. True if the user was notified, False otherwise.
|
|
"""
|
|
# called when options.auto == True
|
|
# someone is logged in, and we have updates.
|
|
# if we haven't notified in a while, notify:
|
|
user_was_notified = False
|
|
lastNotifiedString = munkicommon.pref('LastNotifiedDate')
|
|
daysBetweenNotifications = munkicommon.pref('DaysBetweenNotifications')
|
|
now = NSDate.new()
|
|
nextNotifyDate = now
|
|
if lastNotifiedString:
|
|
lastNotifiedDate = NSDate.dateWithString_(lastNotifiedString)
|
|
interval = daysBetweenNotifications * (24 * 60 * 60)
|
|
if daysBetweenNotifications > 0:
|
|
# we make this adjustment so a 'daily' notification
|
|
# doesn't require 24 hours to elapse
|
|
# subtract 6 hours
|
|
interval = interval - (6 * 60 * 60)
|
|
nextNotifyDate = lastNotifiedDate.dateByAddingTimeInterval_(interval)
|
|
if now.timeIntervalSinceDate_(nextNotifyDate) > 0:
|
|
# record current notification date
|
|
cmd = ['/usr/bin/defaults', 'write',
|
|
munkicommon.MANAGED_INSTALLS_PLIST_PATH_NO_EXT,
|
|
'LastNotifiedDate', '-date', str(now)]
|
|
unused_retcode = subprocess.call(cmd)
|
|
|
|
# Kill Managed Software Update.app if it's already
|
|
# open so it will update its display
|
|
# using subprocess.Popen instead of subprocess.call
|
|
# so stderr doesn't get output to the terminal
|
|
# when there is no Managed Software Update process running
|
|
cmd = ['/usr/bin/killall', 'Managed Software Update']
|
|
proc = subprocess.Popen(cmd, shell=False, stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
(unused_output, unused_err) = proc.communicate()
|
|
|
|
# notify user of available updates using LaunchAgent to start
|
|
# Managed Software Update.app in the user context.
|
|
launchfile = '/var/run/com.googlecode.munki.ManagedSoftwareUpdate'
|
|
cmd = ['/usr/bin/touch', launchfile]
|
|
unused_retcode = subprocess.call(cmd)
|
|
|
|
time.sleep(0.1)
|
|
if os.path.exists(launchfile):
|
|
os.unlink(launchfile)
|
|
user_was_notified = True
|
|
return user_was_notified
|
|
|
|
|
|
def main():
|
|
"""Main"""
|
|
# check to see if we're root
|
|
if os.geteuid() != 0:
|
|
print >> sys.stderr, 'You must run this as root!'
|
|
exit(-1)
|
|
|
|
# save this for later
|
|
scriptdir = os.path.realpath(os.path.dirname(sys.argv[0]))
|
|
|
|
p = optparse.OptionParser()
|
|
p.set_usage("""Usage: %prog [options]""")
|
|
p.add_option('--auto', '-a', action='store_true',
|
|
help="""Used by launchd LaunchAgent for scheduled runs.
|
|
No user feedback or intervention. All other options
|
|
ignored.""")
|
|
p.add_option('--logoutinstall', '-l', action='store_true',
|
|
help="""Used by launchd LaunchAgent when running at the
|
|
loginwindow.""")
|
|
p.add_option('--installwithnologout', action='store_true',
|
|
help="""Used by Managed Software Update.app when user
|
|
triggers an install without logging out.""")
|
|
p.add_option('--manualcheck', action='store_true',
|
|
help="""Used by launchd LaunchAgent when checking
|
|
manually.""")
|
|
p.add_option('--munkistatusoutput', '-m', action='store_true',
|
|
help="""Uses MunkiStatus.app for progress feedback when
|
|
installing.""")
|
|
p.add_option('--id', default='',
|
|
help='Alternate identifier for catalog retreival')
|
|
p.add_option('--quiet', '-q', action='store_true',
|
|
help="""Quiet mode. Logs messages, but nothing to stdout.
|
|
--verbose is ignored if --quiet is used.""")
|
|
p.add_option('--verbose', '-v', action='count', default=1,
|
|
help="""More verbose output. May be specified multiple
|
|
times.""")
|
|
p.add_option('--checkonly', action='store_true',
|
|
help="""Check for updates, but don't install them.
|
|
This is the default behavior.""")
|
|
p.add_option('--installonly', action='store_true',
|
|
help='Skip checking and install any pending updates.')
|
|
p.add_option('--version', '-V', action='store_true',
|
|
help='Print the version of the munki tools and exit.')
|
|
|
|
options, arguments = p.parse_args()
|
|
runtype = 'custom'
|
|
|
|
checkandinstallatstartupflag = \
|
|
'/Users/Shared/.com.googlecode.munki.checkandinstallatstartup'
|
|
installatstartupflag = \
|
|
'/Users/Shared/.com.googlecode.munki.installatstartup'
|
|
installatlogoutflag = '/private/tmp/com.googlecode.munki.installatlogout'
|
|
|
|
if options.version:
|
|
print munkicommon.get_version()
|
|
exit(0)
|
|
|
|
if options.auto:
|
|
# typically invoked by a launch daemon periodically.
|
|
# munkistatusoutput is false for checking, but true for installing
|
|
runtype = 'auto'
|
|
options.munkistatusoutput = False
|
|
options.quiet = True
|
|
options.checkonly = False
|
|
options.installonly = False
|
|
|
|
if options.logoutinstall:
|
|
# typically invoked by launchd agent
|
|
# running in the LoginWindow context
|
|
runtype = 'logoutinstall'
|
|
options.munkistatusoutput = True
|
|
options.quiet = True
|
|
options.checkonly = False
|
|
options.installonly = True
|
|
# if we're running at the loginwindow,
|
|
# let's make sure the user triggered
|
|
# the update before logging out, or we triggered it before restarting.
|
|
user_triggered = False
|
|
flagfiles = [checkandinstallatstartupflag,
|
|
installatstartupflag,
|
|
installatlogoutflag]
|
|
for filename in flagfiles:
|
|
if os.path.exists(filename):
|
|
user_triggered = True
|
|
if filename == checkandinstallatstartupflag:
|
|
runtype = 'checkandinstallatstartup'
|
|
options.installonly = False
|
|
options.auto = True
|
|
# HACK: sometimes this runs before the network is up.
|
|
# we'll attempt to wait up to 10 seconds for the
|
|
# network interfaces to come up
|
|
# before continuing
|
|
munkicommon.display_status('Waiting for network...')
|
|
for i in range(5):
|
|
if networkUp():
|
|
break
|
|
time.sleep(2)
|
|
else:
|
|
# delete triggerfile if _not_ checkandinstallatstartup
|
|
os.unlink(filename)
|
|
if not user_triggered:
|
|
exit(0)
|
|
|
|
if options.installwithnologout:
|
|
# typically invoked by Managed Software Update.app
|
|
# by user who decides not to logout
|
|
launchdtriggerfile = \
|
|
'/private/tmp/.com.googlecode.munki.managedinstall.launchd'
|
|
if os.path.exists(launchdtriggerfile):
|
|
# remove it so we aren't automatically relaunched
|
|
os.unlink(launchdtriggerfile)
|
|
runtype = 'installwithnologout'
|
|
options.munkistatusoutput = True
|
|
options.quiet = True
|
|
options.checkonly = False
|
|
options.installonly = True
|
|
|
|
if options.manualcheck:
|
|
# triggered by Managed Software Update.app
|
|
launchdtriggerfile = \
|
|
'/private/tmp/.com.googlecode.munki.updatecheck.launchd'
|
|
if os.path.exists(launchdtriggerfile):
|
|
# remove it so we aren't automatically relaunched
|
|
os.unlink(launchdtriggerfile)
|
|
runtype = 'manualcheck'
|
|
options.munkistatusoutput = True
|
|
options.quiet = True
|
|
options.checkonly = True
|
|
options.installonly = False
|
|
|
|
if options.quiet:
|
|
options.verbose = 0
|
|
|
|
if options.checkonly and options.installonly:
|
|
print >> sys.stderr, \
|
|
'--checkonly and --installonly options are mutually exclusive!'
|
|
exit(-1)
|
|
|
|
# run the preflight script if it exists
|
|
preflightscript = os.path.join(scriptdir, 'preflight')
|
|
try:
|
|
result, output = utils.runExternalScript(preflightscript, runtype)
|
|
munkicommon.display_info(output)
|
|
except utils.ScriptNotFoundError:
|
|
result = 0
|
|
pass # script is not required, so pass
|
|
except utils.RunExternalScriptError, e:
|
|
result = 0
|
|
munkicommon.display_warning(msg)
|
|
|
|
if result:
|
|
# non-zero return code means don't run
|
|
munkicommon.display_info('managedsoftwareupdate run aborted by'
|
|
' preflight script: %s' % result)
|
|
# record the check result for use by Managed Software Update.app
|
|
# right now, we'll return the same code as if the munki server
|
|
# was unavailable. We need to revisit this and define additional
|
|
# update check results.
|
|
recordUpdateCheckResult(-2)
|
|
if options.munkistatusoutput:
|
|
# connect to socket and quit
|
|
munkistatus.activate()
|
|
munkistatus.quit()
|
|
exit(-1)
|
|
# Force a prefs refresh, in case preflight modified the prefs file.
|
|
munkicommon.prefs(force_refresh=True)
|
|
|
|
# set munkicommon globals
|
|
munkicommon.munkistatusoutput = options.munkistatusoutput
|
|
munkicommon.verbose = options.verbose
|
|
|
|
# create needed directories if necessary
|
|
if not initMunkiDirs():
|
|
exit(-1)
|
|
|
|
# check to see if another instance of this script is running
|
|
myname = os.path.basename(sys.argv[0])
|
|
if munkicommon.pythonScriptRunning(myname):
|
|
# another instance of this script is running, so we should quit
|
|
if options.manualcheck:
|
|
# a manual update check was triggered
|
|
# (probably by Managed Software Update), but managedsoftwareupdate
|
|
# is already running. We should provide user feedback
|
|
munkistatus.activate()
|
|
munkistatus.message('Checking for available updates...')
|
|
while True:
|
|
# loop til the other instance exits
|
|
if not munkicommon.pythonScriptRunning(myname):
|
|
break
|
|
# or user clicks Stop
|
|
if munkistatus.getStopButtonState() == 1:
|
|
break
|
|
time.sleep(0.5)
|
|
|
|
munkistatus.quit()
|
|
else:
|
|
print >> sys.stderr, \
|
|
'Another instance of %s is running. Exiting.' % myname
|
|
exit(0)
|
|
|
|
applesoftwareupdatesonly = munkicommon.pref('AppleSoftwareUpdatesOnly')
|
|
if not options.installonly and not applesoftwareupdatesonly:
|
|
# check to see if we can talk to the manifest server
|
|
server = munkicommon.pref('ManifestURL') or \
|
|
munkicommon.pref('SoftwareRepoURL')
|
|
result = updatecheck.checkServer(server)
|
|
if result != (0, 'OK'):
|
|
munkicommon.display_error('managedsoftwareupdate: '
|
|
'server check for %s failed: %s' %
|
|
(server, str(result)))
|
|
if options.manualcheck:
|
|
# record our result
|
|
recordUpdateCheckResult(-1)
|
|
# connect to socket and quit
|
|
munkistatus.activate()
|
|
munkistatus.quit()
|
|
exit(-1)
|
|
|
|
# reset our errors and warnings files, rotate main log if needed
|
|
munkicommon.reset_errors()
|
|
munkicommon.reset_warnings()
|
|
munkicommon.rotate_main_log()
|
|
munkicommon.archive_report()
|
|
# start a new report
|
|
munkicommon.report['StartTime'] = munkicommon.format_time()
|
|
munkicommon.report['RunType'] = runtype
|
|
|
|
munkicommon.log("### Starting managedsoftwareupdate run ###")
|
|
if options.verbose:
|
|
print 'Managed Software Update Tool'
|
|
print 'Copyright 2010 The Munki Project'
|
|
print 'http://code.google.com/p/munki\n'
|
|
|
|
if applesoftwareupdatesonly and options.verbose:
|
|
print ('NOTE: managedsoftwareupdate is configured to process Apple '
|
|
'Software Updates only.')
|
|
|
|
updatecheckresult = None
|
|
if not options.installonly and not applesoftwareupdatesonly:
|
|
try:
|
|
updatecheckresult = updatecheck.check(client_id=options.id)
|
|
except:
|
|
munkicommon.display_error('Unexpected error in updatecheck:')
|
|
munkicommon.display_error(traceback.format_exc())
|
|
munkicommon.savereport()
|
|
exit(-1)
|
|
|
|
if updatecheckresult is not None:
|
|
recordUpdateCheckResult(updatecheckresult)
|
|
|
|
updatesavailable = munkiUpdatesAvailable()
|
|
if (not updatesavailable and not options.installonly and
|
|
not munkicommon.stopRequested()):
|
|
# if there are no munki updates,
|
|
# are we supposed to check for and install Apple Software Updates?
|
|
if (munkicommon.pref('InstallAppleSoftwareUpdates') or
|
|
applesoftwareupdatesonly):
|
|
try:
|
|
if appleupdates.appleSoftwareUpdatesAvailable(
|
|
forcecheck=(options.manualcheck or
|
|
runtype == 'checkandinstallatstartup')):
|
|
updatesavailable = True
|
|
except:
|
|
munkicommon.display_error('Unexpected error in appleupdates:')
|
|
munkicommon.display_error(traceback.format_exc())
|
|
munkicommon.savereport()
|
|
exit(-1)
|
|
|
|
if (not updatesavailable and options.installonly and
|
|
(munkicommon.pref('InstallAppleSoftwareUpdates') or
|
|
applesoftwareupdatesonly)):
|
|
# just look and see if there are already downloaded Apple updates
|
|
# to install; don't run softwareupdate to check with Apple
|
|
try:
|
|
if appleupdates.appleSoftwareUpdatesAvailable(suppresscheck=True):
|
|
updatesavailable = True
|
|
except:
|
|
munkicommon.display_error('Unexpected error in appleupdates:')
|
|
munkicommon.display_error(traceback.format_exc())
|
|
munkicommon.savereport()
|
|
exit(-1)
|
|
|
|
mustrestart = False
|
|
if options.manualcheck:
|
|
# just quit munkistatus; Managed Software Update will notify
|
|
munkistatus.quit()
|
|
elif updatesavailable:
|
|
if options.installonly or options.logoutinstall:
|
|
# just install
|
|
mustrestart = doInstallTasks()
|
|
elif options.auto:
|
|
if not munkicommon.currentGUIusers(): # no GUI users
|
|
if getIdleSeconds() > 10:
|
|
if not munkicommon.pref('SuppressAutoInstall'):
|
|
# no GUI users, system is idle, so install
|
|
# enable status output over login window
|
|
munkicommon.munkistatusoutput = True
|
|
mustrestart = doInstallTasks()
|
|
else:
|
|
munkicommon.log('Skipping auto install because '
|
|
'SuppressAutoInstall is true.')
|
|
else:
|
|
munkicommon.log('Skipping auto install because system is '
|
|
'not idle (keyboard or mouse activity).')
|
|
else: # there are GUI users
|
|
doInstallTasks(only_forced=True)
|
|
# it's possible that we no longer have any available updates
|
|
# so we need to check InstallInfo.plist again
|
|
if munkiUpdatesAvailable():
|
|
consoleuser = munkicommon.getconsoleuser()
|
|
if consoleuser == u'loginwindow':
|
|
# someone is logged in, but we're sitting at
|
|
# the loginwindow due to fast user switching
|
|
# so do nothing
|
|
pass
|
|
elif not munkicommon.pref('SuppressUserNotification'):
|
|
notifyUserOfUpdates()
|
|
else:
|
|
munkicommon.log('Skipping user notification because '
|
|
'SuppressUserNotification is true.')
|
|
|
|
elif not options.quiet:
|
|
print ('\nRun %s --installonly to install the downloaded '
|
|
'updates.' % myname)
|
|
else:
|
|
# no updates available
|
|
if options.installonly and not options.quiet:
|
|
print 'Nothing to install or remove.'
|
|
if runtype == 'checkandinstallatstartup':
|
|
# we have nothing to do, so remove the
|
|
# checkandinstallatstartupflag file
|
|
# so we'll stop running at startup/logout
|
|
if os.path.exists(checkandinstallatstartupflag):
|
|
os.unlink(checkandinstallatstartupflag)
|
|
|
|
munkicommon.log("### Ending managedsoftwareupdate run ###")
|
|
# finish our report
|
|
munkicommon.report['EndTime'] = munkicommon.format_time()
|
|
munkicommon.report['ManagedInstallVersion'] = munkicommon.get_version()
|
|
munkicommon.report['AvailableDiskSpace'] = \
|
|
munkicommon.getAvailableDiskSpace()
|
|
munkicommon.report['ConsoleUser'] = munkicommon.getconsoleuser() or \
|
|
'<None>'
|
|
munkicommon.savereport()
|
|
|
|
# run the postflight script if it exists
|
|
postflightscript = os.path.join(scriptdir, 'postflight')
|
|
try:
|
|
unused_r, output = utils.runExternalScript(postflightscript, runtype)
|
|
munkicommon.display_info(output)
|
|
except utils.ScriptNotFoundError:
|
|
pass # script is not required, so pass
|
|
except utils.RunExternalScriptError, e:
|
|
munkicommon.display_warning(msg)
|
|
# we ignore the result of the postflight
|
|
|
|
if munkicommon.tmpdir:
|
|
munkicommon.cleanUpTmpDir()
|
|
if mustrestart:
|
|
doRestart()
|
|
elif munkicommon.munkistatusoutput:
|
|
munkistatus.quit()
|
|
|
|
if runtype == 'checkandinstallatstartup' and not mustrestart:
|
|
if os.path.exists(checkandinstallatstartupflag):
|
|
# we installed things but did not need to restart; we need to run
|
|
# again to check for more updates.
|
|
if not munkicommon.currentGUIusers() and getIdleSeconds() > 10:
|
|
# no-one is logged in and the machine has been idle for a few
|
|
# seconds; kill the loginwindow
|
|
# (which will cause us to run again)
|
|
munkicommon.log('Killing loginwindow so we will run again...')
|
|
cmd = ['/usr/bin/killall', 'loginwindow']
|
|
unused_retcode = subprocess.call(cmd)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|