mirror of
https://github.com/munki/munki.git
synced 2026-05-03 19:10:21 -05:00
Added support for installcheck and managedinstaller to handle Apple Software Updates.
Moved some functions from makepkginfo to munkilib so installcheck could use them. Added checking to installcheck, managedinstaller, and removepackages.py so they warn and exit if run by a non-root user. Added checks to installcheck and mamangedinstaller so that they exit if the other is running. git-svn-id: http://munki.googlecode.com/svn/trunk@87 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
The actual tools are:
|
||||
|
||||
installcheck - gets the manifest for the client and processes it to determine what, if anything, needs to be installed.
|
||||
ManagedInstaller - meant to be run as part of a logout hook or as a launchd job. Does the actual installs and removals.
|
||||
managedinstaller - meant to be run as part of a logout hook or as a launchd job. Does the actual installs and removals.
|
||||
ManagedSoftwareUpdate.app - user notification tool.
|
||||
MunkiStatus - used by ManagedInstaller to provide user feedback on the installation and removal process.
|
||||
MunkiStatus.app - used by ManagedInstaller to provide user feedback on the installation and removal process.
|
||||
makepkginfo: Helper tool to help create info files for each installer item.
|
||||
makecatalogs: Creates the software catalogs from the pkginfo files.
|
||||
|
||||
|
||||
|
||||
Supporting libraries:
|
||||
managedinstalls.py - shared functions
|
||||
munkilib.py - shared functions
|
||||
munkistatus.py - functions to display status using MunkiStatus.app. Can also be called directly as a command-line tool.
|
||||
removepackages.py: used by ManagedInstaller to do package removals. Can also be called directly as a command-line tool.
|
||||
|
||||
+104
-11
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python
|
||||
# encoding: utf-8
|
||||
#
|
||||
# Copyright 2009 Greg Neagle.
|
||||
@@ -41,6 +41,7 @@ import socket
|
||||
|
||||
#our lib
|
||||
import munkilib
|
||||
from munkistatus import osascript
|
||||
|
||||
|
||||
def log(message):
|
||||
@@ -1146,6 +1147,78 @@ def getRemovalCount(installinfo):
|
||||
return count
|
||||
|
||||
|
||||
def doSoftwareUpdate(installinfo):
|
||||
installinfo['apple_updates'] = []
|
||||
if munkilib.pref('do_apple_softwareupdate'):
|
||||
|
||||
# switch to our preferred Software Update Server if supplied
|
||||
if munkilib.pref('softwareupdateserver_url'):
|
||||
oldsuserver = ''
|
||||
cmd = ['/usr/bin/defaults', 'read', '/Library/Preferences/com.apple.SoftwareUpdate', 'CatalogURL']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
if p.returncode == 0:
|
||||
oldsusserver = out.rstrip('\n')
|
||||
|
||||
cmd = ['/usr/bin/defaults', 'write', '/Library/Preferences/com.apple.SoftwareUpdate',
|
||||
'CatalogURL', munkilib.pref('softwareupdateserver_url')]
|
||||
retcode = subprocess.call(cmd)
|
||||
|
||||
swupdldir = '/var/root/Downloads'
|
||||
runcheck = True
|
||||
# check downloads dir and skip checking if there's
|
||||
# anything in it
|
||||
for item in os.listdir(swupdldir):
|
||||
if item.endswith('.pkg') or item.endswith('.mpkg'):
|
||||
runcheck = False
|
||||
break
|
||||
if runcheck:
|
||||
cmd = ['/usr/sbin/softwareupdate', '-d', '-a']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
while (p.poll() == None):
|
||||
swupdout = p.stdout.readline()
|
||||
print swupdout.rstrip("\n")
|
||||
retcode = p.poll()
|
||||
if retcode:
|
||||
# some problem occurred.
|
||||
# right now, we'll just stumble through in case some downloads
|
||||
# succeeded
|
||||
pass
|
||||
|
||||
# now check for downloads and process
|
||||
for item in os.listdir(swupdldir):
|
||||
if item.endswith('.pkg') or item.endswith('.mpkg'):
|
||||
pl = munkilib.getPackageMetaData(os.path.join(swupdldir,item))
|
||||
if pl:
|
||||
# check to see if there is enough free space to install
|
||||
if enoughDiskSpace(pl):
|
||||
iteminfo = {}
|
||||
iteminfo["installer_item"] = item
|
||||
iteminfo["name"] = pl.get('name', '')
|
||||
iteminfo["description"] = pl.get('description', '')
|
||||
if iteminfo["description"] == '':
|
||||
iteminfo["description"] = "Updated Apple software."
|
||||
iteminfo["version_to_install"] = pl.get('version',"UNKNOWN")
|
||||
iteminfo['display_name'] = pl.get('display_name','')
|
||||
if 'RestartAction' in pl:
|
||||
iteminfo['RestartAction'] = pl['RestartAction']
|
||||
installinfo['apple_updates'].append(iteminfo)
|
||||
|
||||
# switch back to original Software Update server
|
||||
if munkilib.pref('softwareupdateserver_url'):
|
||||
if oldsuserver:
|
||||
cmd = ['/usr/bin/defaults', 'write', '/Library/Preferences/com.apple.SoftwareUpdate',
|
||||
'CatalogURL', oldsuserver]
|
||||
else:
|
||||
cmd = ['/usr/bin/defaults', 'delete', '/Library/Preferences/com.apple.SoftwareUpdate']
|
||||
retcode = subprocess.call(cmd)
|
||||
|
||||
return installinfo
|
||||
|
||||
|
||||
def checkServer():
|
||||
managedinstallprefs = munkilib.prefs()
|
||||
manifesturl = managedinstallprefs['manifest_url']
|
||||
@@ -1169,9 +1242,6 @@ def checkServer():
|
||||
#except:
|
||||
#return False
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# some globals
|
||||
@@ -1192,6 +1262,11 @@ options, arguments = p.parse_args()
|
||||
def main():
|
||||
global mytmpdir, options, logdir, errors
|
||||
|
||||
# check to see if we're root
|
||||
if os.geteuid() != 0:
|
||||
print >>sys.stderr, "You must run this as root!"
|
||||
exit(-1)
|
||||
|
||||
if not options.quiet: print "Managed Software Check\n"
|
||||
|
||||
managedinstallprefs = munkilib.prefs()
|
||||
@@ -1210,6 +1285,12 @@ def main():
|
||||
exit(-1)
|
||||
log("### Beginning managed software check ###")
|
||||
|
||||
if munkilib.pythonScriptRunning("managedinstaller"):
|
||||
# managedinstaller is running, so we should quit
|
||||
printandlog("managedinstaller is running. Exiting.")
|
||||
log("### End managed software check ###")
|
||||
exit(0)
|
||||
|
||||
mainmanifestpath = getPrimaryManifest(options.id)
|
||||
if not mainmanifestpath:
|
||||
logerror("Could not retreive managed install primary manifest.")
|
||||
@@ -1247,6 +1328,9 @@ def main():
|
||||
printandlog("**Checking for removals**", 1)
|
||||
installinfo = processManifestForRemovals(mainmanifestpath, installinfo)
|
||||
|
||||
printandlog("**Checking for Apple Software Updates**", 1)
|
||||
installinfo = doSoftwareUpdate(installinfo)
|
||||
|
||||
# need to write out install list so the autoinstaller
|
||||
# can use it to install things in the right order
|
||||
installinfochanged = True
|
||||
@@ -1268,13 +1352,8 @@ def main():
|
||||
|
||||
installcount = getInstallCount(installinfo)
|
||||
removalcount = getRemovalCount(installinfo)
|
||||
appleupdatecount = len(installinfo.get('apple_updates',[]))
|
||||
|
||||
if installinfochanged and not options.quiet:
|
||||
if installcount or removalcount:
|
||||
# here's where we could trigger the ManagedInstaller or ManagedSoftwareUpdate tools
|
||||
# print "Managed software updates are available."
|
||||
pass
|
||||
|
||||
if installcount:
|
||||
printandlog("The following items will be installed or upgraded:")
|
||||
for item in installinfo['managed_installs']:
|
||||
@@ -1291,12 +1370,26 @@ def main():
|
||||
printandlog(" - %s" % item.get('name'))
|
||||
if item.get('RestartAction') == 'RequireRestart':
|
||||
printandlog(" *Restart required")
|
||||
if appleupdatecount:
|
||||
printandlog("The following Apple updates will be installed:")
|
||||
for item in installinfo['apple_updates']:
|
||||
printandlog (" + %s-%s" % (item.get('name'), item.get('version_to_install')))
|
||||
if item.get('description') != "Updated Apple software.":
|
||||
printandlog(" %s" % item['description'])
|
||||
if item.get('RestartAction') == 'RequireRestart':
|
||||
printandlog(" *Restart required")
|
||||
|
||||
if errors:
|
||||
reporterrors()
|
||||
|
||||
if installcount == 0 and removalcount == 0:
|
||||
if installcount == 0 and removalcount == 0 and appleupdatecount == 0:
|
||||
printandlog("No changes to managed software scheduled.")
|
||||
else:
|
||||
if munkilib.getconsoleuser() == None:
|
||||
pass
|
||||
else:
|
||||
result = osascript('tell application "Managed Software Update" to activate')
|
||||
|
||||
|
||||
log("### End managed software check ###")
|
||||
|
||||
|
||||
+5
-119
@@ -44,120 +44,6 @@ import hashlib
|
||||
import munkilib
|
||||
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
"""
|
||||
Attempts to mount the dmg at dmgpath
|
||||
and returns a list of mountpoints
|
||||
"""
|
||||
mountpoints = []
|
||||
dmgname = os.path.basename(dmgpath)
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-mountRandom', '/tmp', '-nobrowse', '-plist'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(plist, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, "Error %s mounting %s." % (err, dmgpath)
|
||||
if plist:
|
||||
pl = plistlib.readPlistFromString(plist)
|
||||
for entity in pl['system-entities']:
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
|
||||
return mountpoints
|
||||
|
||||
|
||||
def unmountdmg(mountpoint):
|
||||
"""
|
||||
Unmounts the dmg at mountpoint
|
||||
"""
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, err
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint, '-force'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
|
||||
|
||||
def nameAndVersion(s):
|
||||
"""
|
||||
Splits a string into the name and version numbers:
|
||||
'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1')
|
||||
'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1')
|
||||
'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1')
|
||||
"""
|
||||
index = 0
|
||||
for char in s:
|
||||
if char in "0123456789":
|
||||
possibleVersion = s[index:]
|
||||
if not (" " in possibleVersion or "_" in possibleVersion or "-" in possibleVersion or "v" in possibleVersion):
|
||||
return (s[0:index].rstrip(" .-_v"), possibleVersion)
|
||||
index += 1
|
||||
# no version number found, just return original string and empty string
|
||||
return (s, '')
|
||||
|
||||
|
||||
def getCatalogInfo(pkgitem):
|
||||
"""
|
||||
The core function here. Queries an installer item (.pkg, .mpkg)
|
||||
and gets metadata. There are a lot of valid Apple package formats
|
||||
and this function may not deal with them all equally well.
|
||||
Standard bundle packages are probably the best understood and documented,
|
||||
so this code deals with those pretty well.
|
||||
|
||||
metadata items include:
|
||||
installer_item_size: size of the installer item (.dmg, .pkg, etc)
|
||||
installed_size: size of items that will be installed
|
||||
RestartAction: will a restart be needed after installation?
|
||||
name
|
||||
version
|
||||
description
|
||||
receipts: an array of packageids that may be installed (some may be optional)
|
||||
"""
|
||||
installedsize = 0
|
||||
installerinfo = munkilib.getInstallerPkgInfo(pkgitem)
|
||||
info = munkilib.getPkgInfo(pkgitem)
|
||||
|
||||
highestpkgversion = "0.0"
|
||||
for infoitem in info:
|
||||
if version.LooseVersion(infoitem['version']) > version.LooseVersion(highestpkgversion):
|
||||
highestpkgversion = infoitem['version']
|
||||
if "installed_size" in infoitem:
|
||||
# note this is in KBytes
|
||||
installedsize += infoitem['installed_size']
|
||||
|
||||
name = os.path.split(pkgitem)[1]
|
||||
shortname = os.path.splitext(name)[0]
|
||||
metaversion = nameAndVersion(shortname)[1]
|
||||
if not len(metaversion):
|
||||
metaversion = highestpkgversion
|
||||
elif highestpkgversion.startswith(metaversion):
|
||||
#for example, highestpkgversion is 2.0.3124.0, version in filename is 2.0
|
||||
metaversion = highestpkgversion
|
||||
|
||||
if 'installed_size' in installerinfo:
|
||||
if installerinfo['installed_size'] > 0:
|
||||
installedsize = installerinfo['installed_size']
|
||||
|
||||
cataloginfo = {}
|
||||
cataloginfo['name'] = nameAndVersion(shortname)[0]
|
||||
cataloginfo['version'] = metaversion
|
||||
for key in ('display_name', 'RestartAction', 'description'):
|
||||
if key in installerinfo:
|
||||
cataloginfo[key] = installerinfo[key]
|
||||
|
||||
if installedsize > 0:
|
||||
cataloginfo['installed_size'] = installedsize
|
||||
|
||||
cataloginfo['receipts'] = []
|
||||
for infoitem in info:
|
||||
pkginfo = {}
|
||||
pkginfo['packageid'] = infoitem['id']
|
||||
pkginfo['version'] = infoitem['version']
|
||||
cataloginfo['receipts'].append(pkginfo)
|
||||
return cataloginfo
|
||||
|
||||
|
||||
def getCatalogInfoFromDmg(dmgpath):
|
||||
"""
|
||||
* Mounts a disk image
|
||||
@@ -167,21 +53,21 @@ def getCatalogInfoFromDmg(dmgpath):
|
||||
To-do: handle multiple installer items on a disk image
|
||||
"""
|
||||
cataloginfo = None
|
||||
mountpoints = mountdmg(dmgpath)
|
||||
mountpoints = munkilib.mountdmg(dmgpath)
|
||||
for mountpoint in mountpoints:
|
||||
for fsitem in os.listdir(mountpoint):
|
||||
itempath = os.path.join(mountpoint, fsitem)
|
||||
if itempath.endswith('.pkg') or itempath.endswith('.mpkg'):
|
||||
cataloginfo = getCatalogInfo(itempath)
|
||||
cataloginfo = munkilib.getPackageMetaData(itempath)
|
||||
# get out of fsitem loop
|
||||
break
|
||||
if cataloginfo:
|
||||
# get out of moutpoint loop
|
||||
# get out of mountpoint loop
|
||||
break
|
||||
|
||||
#unmount all the mountpoints from the dmg
|
||||
for mountpoint in mountpoints:
|
||||
unmountdmg(mountpoint)
|
||||
munkilib.unmountdmg(mountpoint)
|
||||
return cataloginfo
|
||||
|
||||
|
||||
@@ -301,7 +187,7 @@ def main():
|
||||
if item.endswith('.dmg'):
|
||||
catinfo = getCatalogInfoFromDmg(item)
|
||||
elif item.endswith('.pkg') or item.endswith('.mpkg'):
|
||||
catinfo = getCatalogInfo(item)
|
||||
catinfo = munkilib.getPackageMetaData(item)
|
||||
else:
|
||||
print >>sys.stderr, "%s is not an installer package!" % item
|
||||
exit(-1)
|
||||
|
||||
@@ -191,10 +191,18 @@ def installall(dirpath):
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if item.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
if not options.munkistatusoutput:
|
||||
print "Mounting disk image %s" % item
|
||||
log("Mounting disk image %s" % item)
|
||||
mountpoints = munkilib.mountdmg(itempath)
|
||||
if mountpoints == []:
|
||||
if not options.munkistatusoutput:
|
||||
print >>sys.stderr, "ERROR: No filesystems mounted from %s" % item
|
||||
log("ERROR: No filesystems mounted from %s" % item)
|
||||
return restartflag
|
||||
if stopRequested():
|
||||
for mountpoint in mountpoints:
|
||||
unmountdmg(mountpoint)
|
||||
munkilib.unmountdmg(mountpoint)
|
||||
return restartflag
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
@@ -202,8 +210,7 @@ def installall(dirpath):
|
||||
needtorestart = installall(mountpoint)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
unmountdmg(mountpoint)
|
||||
|
||||
munkilib.unmountdmg(mountpoint)
|
||||
return restartflag
|
||||
|
||||
|
||||
@@ -229,16 +236,27 @@ def installWithInfo(dirpath, installlist):
|
||||
itempath = os.path.join(dirpath, item["installer_item"])
|
||||
if not os.path.exists(itempath):
|
||||
#can't install, so we should stop
|
||||
errmsg = "Installer item %s was not found." % item["installer_item"]
|
||||
print >>sys.stderr, errmsg
|
||||
log(errmsg)
|
||||
return restartflag
|
||||
if (itempath.endswith(".pkg") or itempath.endswith(".mpkg")):
|
||||
(retcode, needsrestart) = install(itempath)
|
||||
if needsrestart:
|
||||
restartflag = True
|
||||
if itempath.endswith(".dmg"):
|
||||
mountpoints = mountdmg(itempath)
|
||||
if not options.munkistatusoutput:
|
||||
print "Mounting disk image %s" % item["installer_item"]
|
||||
log("Mounting disk image %s" % item["installer_item"])
|
||||
mountpoints = munkilib.mountdmg(itempath)
|
||||
if mountpoints == []:
|
||||
if not options.munkistatusoutput:
|
||||
print >>sys.stderr, "ERROR: No filesystems mounted from %s" % item["installer_item"]
|
||||
log("ERROR: No filesystems mounted from %s" % item["installer_item"])
|
||||
return restartflag
|
||||
if stopRequested():
|
||||
for mountpoint in mountpoints:
|
||||
unmountdmg(mountpoint)
|
||||
munkilib.unmountdmg(mountpoint)
|
||||
return restartflag
|
||||
for mountpoint in mountpoints:
|
||||
# install all the pkgs and mpkgs at the root
|
||||
@@ -246,7 +264,7 @@ def installWithInfo(dirpath, installlist):
|
||||
needtorestart = installall(mountpoint)
|
||||
if needtorestart:
|
||||
restartflag = True
|
||||
unmountdmg(mountpoint)
|
||||
munkilib.unmountdmg(mountpoint)
|
||||
|
||||
# now remove the item from the install cache
|
||||
# (using rm -f in case it's a bundle pkg)
|
||||
@@ -354,45 +372,9 @@ def processRemovals(removalList):
|
||||
log("Uninstall of %s failed because there was no valid uninstall method." % name)
|
||||
|
||||
return restartFlag
|
||||
|
||||
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
"""
|
||||
Attempts to mount the dmg at dmgpath
|
||||
and returns a list of mountpoints
|
||||
"""
|
||||
mountpoints = []
|
||||
dmgname = os.path.basename(dmgpath)
|
||||
if not options.munkistatusoutput:
|
||||
print "Mounting disk image %s" % dmgname
|
||||
log("Mounting disk image %s" % dmgname)
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-mountRandom', '/tmp', '-nobrowse', '-plist'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(plist, err) = p.communicate()
|
||||
if plist:
|
||||
pl = plistlib.readPlistFromString(plist)
|
||||
for entity in pl['system-entities']:
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
|
||||
return mountpoints
|
||||
|
||||
|
||||
def unmountdmg(mountpoint):
|
||||
"""
|
||||
Unmounts the dmg at mountpoint
|
||||
"""
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, err
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint, '-force'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
|
||||
|
||||
|
||||
# module (global) variables
|
||||
logdir = None
|
||||
options = None
|
||||
@@ -400,6 +382,12 @@ options = None
|
||||
|
||||
def main():
|
||||
global logdir, options
|
||||
|
||||
# check to see if we're root
|
||||
if os.geteuid() != 0:
|
||||
print >>sys.stderr, "You must run this as root!"
|
||||
exit(-1)
|
||||
|
||||
managedinstallbase = munkilib.managed_install_dir()
|
||||
installdir = os.path.join(managedinstallbase , 'Cache')
|
||||
logdir = os.path.join(managedinstallbase, 'Logs')
|
||||
@@ -411,6 +399,14 @@ def main():
|
||||
needtorestart = False
|
||||
createDirsIfNeeded([logdir])
|
||||
log("### Beginning managed installer session ###")
|
||||
|
||||
if munkilib.pythonScriptRunning("installcheck"):
|
||||
# installcheck is running, so we should quit
|
||||
print "installcheck is running. Exiting."
|
||||
log("installcheck is running. Exiting.")
|
||||
log("### End managed installer session ###")
|
||||
exit(0)
|
||||
|
||||
installinfo = os.path.join(managedinstallbase, 'InstallInfo.plist')
|
||||
if os.path.exists(installinfo):
|
||||
try:
|
||||
@@ -438,6 +434,8 @@ def main():
|
||||
if "managed_installs" in pl:
|
||||
if not stopRequested():
|
||||
installcount = getInstallCount(pl['managed_installs'])
|
||||
if "apple_updates" in pl:
|
||||
installcount = installcount + len(pl['apple_updates'])
|
||||
if installcount:
|
||||
if options.munkistatusoutput:
|
||||
if installcount == 1:
|
||||
@@ -448,6 +446,11 @@ def main():
|
||||
munkistatus.percent(-1)
|
||||
log("Processing installs")
|
||||
needtorestart = installWithInfo(installdir, pl['managed_installs'])
|
||||
if "apple_updates" in pl:
|
||||
if not stopRequested():
|
||||
log("Installing Apple updates")
|
||||
swupdldir = '/var/root/Downloads'
|
||||
needtorestart = installWithInfo(swupdldir, pl['apple_updates'])
|
||||
|
||||
else:
|
||||
log("No %s found." % installinfo)
|
||||
|
||||
+145
-6
@@ -32,14 +32,69 @@ import calendar
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
from distutils import version
|
||||
from xml.dom import minidom
|
||||
|
||||
from SystemConfiguration import *
|
||||
|
||||
|
||||
# misc functions
|
||||
|
||||
def getconsoleuser():
|
||||
cfuser = SCDynamicStoreCopyConsoleUser( None, None, None )
|
||||
return cfuser[0]
|
||||
|
||||
|
||||
def pythonScriptRunning(scriptname):
|
||||
cmd = ['/bin/ps', '-eo', 'command=']
|
||||
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(out, err) = p.communicate()
|
||||
lines = out.splitlines()
|
||||
for line in lines:
|
||||
# first look for Python processes
|
||||
if line.find("MacOS/Python ") != -1:
|
||||
if line.find(scriptname) != -1:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# dmg helpers
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
"""
|
||||
Attempts to mount the dmg at dmgpath
|
||||
and returns a list of mountpoints
|
||||
"""
|
||||
mountpoints = []
|
||||
dmgname = os.path.basename(dmgpath)
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'attach', dmgpath, '-mountRandom', '/tmp', '-nobrowse', '-plist'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(plist, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, "Error %s mounting %s." % (err, dmgpath)
|
||||
if plist:
|
||||
pl = plistlib.readPlistFromString(plist)
|
||||
for entity in pl['system-entities']:
|
||||
if 'mount-point' in entity:
|
||||
mountpoints.append(entity['mount-point'])
|
||||
|
||||
return mountpoints
|
||||
|
||||
|
||||
def unmountdmg(mountpoint):
|
||||
"""
|
||||
Unmounts the dmg at mountpoint
|
||||
"""
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
if err:
|
||||
print >>sys.stderr, err
|
||||
p = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint, '-force'],
|
||||
bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(output, err) = p.communicate()
|
||||
|
||||
|
||||
#####################################################
|
||||
@@ -55,8 +110,10 @@ def getManagedInstallsPrefs():
|
||||
prefs['sw_repo_url'] = "http:/munki/repo"
|
||||
prefs['client_identifier'] = ""
|
||||
prefs['logging_level'] = 1
|
||||
prefs['do_apple_softwareupdate'] = False
|
||||
prefs['softwareupdateserver_url'] = None
|
||||
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
|
||||
|
||||
pl = {}
|
||||
if os.path.exists(prefsfile):
|
||||
try:
|
||||
pl = plistlib.readPlist(prefsfile)
|
||||
@@ -73,6 +130,10 @@ def getManagedInstallsPrefs():
|
||||
prefs['client_identifier'] = pl['client_identifier']
|
||||
if 'logging_level' in pl:
|
||||
prefs['logging_level'] = pl['logging_level']
|
||||
if 'do_apple_softwareupdate' in pl:
|
||||
prefs['do_apple_softwareupdate'] = pl['do_apple_softwareupdate']
|
||||
if 'softwareupdateserver_url' in pl:
|
||||
prefs['softwareupdateserver_url'] = pl['softwareupdateserver_url']
|
||||
|
||||
|
||||
return prefs
|
||||
@@ -94,11 +155,7 @@ def sw_repo_url():
|
||||
|
||||
|
||||
def pref(prefname):
|
||||
prefs = getManagedInstallsPrefs()
|
||||
if prefname in prefs:
|
||||
return prefs[prefname]
|
||||
else:
|
||||
return ''
|
||||
return getManagedInstallsPrefs().get(prefname)
|
||||
|
||||
|
||||
def prefs():
|
||||
@@ -346,6 +403,88 @@ def getInstalledPackageVersion(pkgid):
|
||||
return ""
|
||||
|
||||
|
||||
def nameAndVersion(s):
|
||||
"""
|
||||
Splits a string into the name and version numbers:
|
||||
'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1')
|
||||
'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1')
|
||||
'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1')
|
||||
"""
|
||||
index = 0
|
||||
for char in s:
|
||||
if char in "0123456789":
|
||||
possibleVersion = s[index:]
|
||||
if not (" " in possibleVersion or "_" in possibleVersion or "-" in possibleVersion or "v" in possibleVersion):
|
||||
return (s[0:index].rstrip(" .-_v"), possibleVersion)
|
||||
index += 1
|
||||
# no version number found, just return original string and empty string
|
||||
return (s, '')
|
||||
|
||||
|
||||
def getPackageMetaData(pkgitem):
|
||||
"""
|
||||
Queries an installer item (.pkg, .mpkg)
|
||||
and gets metadata. There are a lot of valid Apple package formats
|
||||
and this function may not deal with them all equally well.
|
||||
Standard bundle packages are probably the best understood and documented,
|
||||
so this code deals with those pretty well.
|
||||
|
||||
metadata items include:
|
||||
installer_item_size: size of the installer item (.dmg, .pkg, etc)
|
||||
installed_size: size of items that will be installed
|
||||
RestartAction: will a restart be needed after installation?
|
||||
name
|
||||
version
|
||||
description
|
||||
receipts: an array of packageids that may be installed (some may be optional)
|
||||
"""
|
||||
installedsize = 0
|
||||
installerinfo = getInstallerPkgInfo(pkgitem)
|
||||
info = getPkgInfo(pkgitem)
|
||||
|
||||
highestpkgversion = "0.0"
|
||||
for infoitem in info:
|
||||
if version.LooseVersion(infoitem['version']) > version.LooseVersion(highestpkgversion):
|
||||
highestpkgversion = infoitem['version']
|
||||
if "installed_size" in infoitem:
|
||||
# note this is in KBytes
|
||||
installedsize += infoitem['installed_size']
|
||||
|
||||
name = os.path.split(pkgitem)[1]
|
||||
shortname = os.path.splitext(name)[0]
|
||||
metaversion = nameAndVersion(shortname)[1]
|
||||
if not len(metaversion):
|
||||
# there is no version number in the filename
|
||||
metaversion = highestpkgversion
|
||||
elif len(info) == 1:
|
||||
# there is only one package in this item
|
||||
metaversion = highestpkgversion
|
||||
elif highestpkgversion.startswith(metaversion):
|
||||
# for example, highestpkgversion is 2.0.3124.0, version in filename is 2.0
|
||||
metaversion = highestpkgversion
|
||||
|
||||
if 'installed_size' in installerinfo:
|
||||
if installerinfo['installed_size'] > 0:
|
||||
installedsize = installerinfo['installed_size']
|
||||
|
||||
cataloginfo = {}
|
||||
cataloginfo['name'] = nameAndVersion(shortname)[0]
|
||||
cataloginfo['version'] = metaversion
|
||||
for key in ('display_name', 'RestartAction', 'description'):
|
||||
if key in installerinfo:
|
||||
cataloginfo[key] = installerinfo[key]
|
||||
|
||||
if installedsize > 0:
|
||||
cataloginfo['installed_size'] = installedsize
|
||||
|
||||
cataloginfo['receipts'] = []
|
||||
for infoitem in info:
|
||||
pkginfo = {}
|
||||
pkginfo['packageid'] = infoitem['id']
|
||||
pkginfo['version'] = infoitem['version']
|
||||
cataloginfo['receipts'].append(pkginfo)
|
||||
return cataloginfo
|
||||
|
||||
# some utility functions
|
||||
|
||||
def getAvailableDiskSpace(volumepath="/"):
|
||||
|
||||
@@ -759,11 +759,6 @@ def removepackages(pkgnames, forcedeletebundles=False, listfiles=False,
|
||||
verbose = kwargs.get('verbose', False)
|
||||
verbose = kwargs.get('verbose', '')
|
||||
|
||||
# check to see if we're root
|
||||
if os.geteuid() != 0:
|
||||
display_error("You must run this as root!")
|
||||
return -1
|
||||
|
||||
if pkgnames == []:
|
||||
display_error("You must specify at least one package to remove!")
|
||||
return -2
|
||||
@@ -831,7 +826,13 @@ def main():
|
||||
help="Path to a log file.")
|
||||
# Get our options and our package names
|
||||
options, pkgnames = p.parse_args()
|
||||
retcode = removePackages(pkgnames, forcedeletebundles=options.forcedeletebundles, listfiles=options.listfiles,
|
||||
|
||||
# check to see if we're root
|
||||
if os.geteuid() != 0:
|
||||
display_error("You must run this as root!")
|
||||
exit(-1)
|
||||
|
||||
retcode = removepackages(pkgnames, forcedeletebundles=options.forcedeletebundles, listfiles=options.listfiles,
|
||||
rebuildpkgdb=options.rebuildpkgdb, noremovereceipts=options.noremovereceipts,
|
||||
noupdateapplepkgdb=options.noupdateapplepkgdb, munkistatusoutput=options.munkistatusoutput,
|
||||
verbose=options.verbose, logfile=options.logfile)
|
||||
|
||||
Reference in New Issue
Block a user