mirror of
https://github.com/grahampugh/macadmin-scripts.git
synced 2025-12-17 17:56:33 +00:00
Add Python 3 support to installinstallmacos.py
This commit is contained in:
parent
7b16cf5d39
commit
b3339ef2f8
@ -23,17 +23,28 @@ A tool to download the parts for an Install macOS app from Apple's
|
||||
softwareupdate servers and install a functioning Install macOS app onto an
|
||||
empty disk image'''
|
||||
|
||||
# Python 2/3 compatibility shims
|
||||
from __future__ import (absolute_import,
|
||||
division,
|
||||
print_function,
|
||||
unicode_literals)
|
||||
from builtins import input
|
||||
|
||||
# pylint: disable=wrong-import-order,wrong-import-position
|
||||
import argparse
|
||||
import gzip
|
||||
import os
|
||||
import plistlib
|
||||
import subprocess
|
||||
import sys
|
||||
import urlparse
|
||||
try:
|
||||
from urllib.parse import urlsplit
|
||||
except ImportError:
|
||||
from urlparse import urlsplit
|
||||
import xattr
|
||||
from xml.dom import minidom
|
||||
from xml.parsers.expat import ExpatError
|
||||
# pylint: enable=wrong-import-order,wrong-import-position
|
||||
|
||||
|
||||
DEFAULT_SUCATALOGS = {
|
||||
@ -56,7 +67,7 @@ def get_seeding_program(sucatalog_url):
|
||||
'''Returns a seeding program name based on the sucatalog_url'''
|
||||
try:
|
||||
seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST)
|
||||
for key, value in seed_catalogs.items():
|
||||
for key, value in list(seed_catalogs.items()):
|
||||
if sucatalog_url == value:
|
||||
return key
|
||||
return ''
|
||||
@ -77,7 +88,7 @@ def get_seeding_programs():
|
||||
'''Returns the list of seeding program names'''
|
||||
try:
|
||||
seed_catalogs = plistlib.readPlist(SEED_CATALOGS_PLIST)
|
||||
return seed_catalogs.keys()
|
||||
return list(seed_catalogs.keys())
|
||||
except (OSError, ExpatError, AttributeError, KeyError):
|
||||
return ''
|
||||
|
||||
@ -94,17 +105,17 @@ def make_sparse_image(volume_name, output_path):
|
||||
'-volname', volume_name, '-type', 'SPARSE', '-plist', output_path]
|
||||
try:
|
||||
output = subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError, err:
|
||||
print >> sys.stderr, err
|
||||
except subprocess.CalledProcessError as err:
|
||||
print(err, file=sys.stderr)
|
||||
exit(-1)
|
||||
try:
|
||||
return plistlib.readPlistFromString(output)[0]
|
||||
except IndexError, err:
|
||||
print >> sys.stderr, 'Unexpected output from hdiutil: %s' % output
|
||||
except IndexError as err:
|
||||
print('Unexpected output from hdiutil: %s' % output, file=sys.stderr)
|
||||
exit(-1)
|
||||
except ExpatError, err:
|
||||
print >> sys.stderr, 'Malformed output from hdiutil: %s' % output
|
||||
print >> sys.stderr, err
|
||||
except ExpatError as err:
|
||||
print('Malformed output from hdiutil: %s' % output, file=sys.stderr)
|
||||
print(err, file=sys.stderr)
|
||||
exit(-1)
|
||||
|
||||
|
||||
@ -112,16 +123,16 @@ def make_compressed_dmg(app_path, diskimagepath):
|
||||
"""Returns path to newly-created compressed r/o disk image containing
|
||||
Install macOS.app"""
|
||||
|
||||
print ('Making read-only compressed disk image containing %s...'
|
||||
% os.path.basename(app_path))
|
||||
print(('Making read-only compressed disk image containing %s...'
|
||||
% os.path.basename(app_path)))
|
||||
cmd = ['/usr/bin/hdiutil', 'create', '-fs', 'HFS+',
|
||||
'-srcfolder', app_path, diskimagepath]
|
||||
try:
|
||||
subprocess.check_call(cmd)
|
||||
except subprocess.CalledProcessError, err:
|
||||
print >> sys.stderr, err
|
||||
except subprocess.CalledProcessError as err:
|
||||
print(err, file=sys.stderr)
|
||||
else:
|
||||
print 'Disk image created at: %s' % diskimagepath
|
||||
print('Disk image created at: %s' % diskimagepath)
|
||||
|
||||
|
||||
def mountdmg(dmgpath):
|
||||
@ -137,7 +148,8 @@ def mountdmg(dmgpath):
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(pliststr, err) = proc.communicate()
|
||||
if proc.returncode:
|
||||
print >> sys.stderr, 'Error: "%s" while mounting %s.' % (err, dmgname)
|
||||
print(
|
||||
'Error: "%s" while mounting %s.' % (err, dmgname), file=sys.stderr)
|
||||
return None
|
||||
if pliststr:
|
||||
plist = plistlib.readPlistFromString(pliststr)
|
||||
@ -157,13 +169,13 @@ def unmountdmg(mountpoint):
|
||||
stderr=subprocess.PIPE)
|
||||
(dummy_output, err) = proc.communicate()
|
||||
if proc.returncode:
|
||||
print >> sys.stderr, 'Polite unmount failed: %s' % err
|
||||
print >> sys.stderr, 'Attempting to force unmount %s' % mountpoint
|
||||
print('Polite unmount failed: %s' % err, file=sys.stderr)
|
||||
print('Attempting to force unmount %s' % mountpoint, file=sys.stderr)
|
||||
# try forcing the unmount
|
||||
retcode = subprocess.call(['/usr/bin/hdiutil', 'detach', mountpoint,
|
||||
'-force'])
|
||||
if retcode:
|
||||
print >> sys.stderr, 'Failed to unmount %s' % mountpoint
|
||||
print('Failed to unmount %s' % mountpoint, file=sys.stderr)
|
||||
|
||||
|
||||
def install_product(dist_path, target_vol):
|
||||
@ -173,8 +185,8 @@ def install_product(dist_path, target_vol):
|
||||
try:
|
||||
subprocess.check_call(cmd)
|
||||
return True
|
||||
except subprocess.CalledProcessError, err:
|
||||
print >> sys.stderr, err
|
||||
except subprocess.CalledProcessError as err:
|
||||
print(err, file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
@ -191,7 +203,7 @@ def replicate_url(full_url,
|
||||
'''Downloads a URL and stores it in the same relative path on our
|
||||
filesystem. Returns a path to the replicated file.'''
|
||||
|
||||
path = urlparse.urlsplit(full_url)[2]
|
||||
path = urlsplit(full_url)[2]
|
||||
relative_url = path.lstrip('/')
|
||||
relative_url = os.path.normpath(relative_url)
|
||||
local_file_path = os.path.join(root_dir, relative_url)
|
||||
@ -206,10 +218,10 @@ def replicate_url(full_url,
|
||||
if attempt_resume:
|
||||
curl_cmd.extend(['-C', '-'])
|
||||
curl_cmd.append(full_url)
|
||||
print "Downloading %s..." % full_url
|
||||
print("Downloading %s..." % full_url)
|
||||
try:
|
||||
subprocess.check_call(curl_cmd)
|
||||
except subprocess.CalledProcessError, err:
|
||||
except subprocess.CalledProcessError as err:
|
||||
raise ReplicationError(err)
|
||||
return local_file_path
|
||||
|
||||
@ -222,8 +234,8 @@ def parse_server_metadata(filename):
|
||||
vers = ''
|
||||
try:
|
||||
md_plist = plistlib.readPlist(filename)
|
||||
except (OSError, IOError, ExpatError), err:
|
||||
print >> sys.stderr, 'Error reading %s: %s' % (filename, err)
|
||||
except (OSError, IOError, ExpatError) as err:
|
||||
print('Error reading %s: %s' % (filename, err), file=sys.stderr)
|
||||
return {}
|
||||
vers = md_plist.get('CFBundleShortVersionString', '')
|
||||
localization = md_plist.get('localization', {})
|
||||
@ -246,12 +258,12 @@ def get_server_metadata(catalog, product_key, workdir, ignore_cache=False):
|
||||
smd_path = replicate_url(
|
||||
url, root_dir=workdir, ignore_cache=ignore_cache)
|
||||
return smd_path
|
||||
except ReplicationError, err:
|
||||
print >> sys.stderr, (
|
||||
'Could not replicate %s: %s' % (url, err))
|
||||
except ReplicationError as err:
|
||||
print((
|
||||
'Could not replicate %s: %s' % (url, err)), file=sys.stderr)
|
||||
return None
|
||||
except KeyError:
|
||||
print >> sys.stderr, 'Malformed catalog.'
|
||||
print('Malformed catalog.', file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
@ -262,10 +274,10 @@ def parse_dist(filename):
|
||||
try:
|
||||
dom = minidom.parse(filename)
|
||||
except ExpatError:
|
||||
print >> sys.stderr, 'Invalid XML in %s' % filename
|
||||
print('Invalid XML in %s' % filename, file=sys.stderr)
|
||||
return dist_info
|
||||
except IOError, err:
|
||||
print >> sys.stderr, 'Error reading %s: %s' % (filename, err)
|
||||
except IOError as err:
|
||||
print('Error reading %s: %s' % (filename, err), file=sys.stderr)
|
||||
return dist_info
|
||||
|
||||
auxinfos = dom.getElementsByTagName('auxinfo')
|
||||
@ -299,8 +311,8 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False):
|
||||
try:
|
||||
localcatalogpath = replicate_url(
|
||||
sucatalog, root_dir=workdir, ignore_cache=ignore_cache)
|
||||
except ReplicationError, err:
|
||||
print >> sys.stderr, 'Could not replicate %s: %s' % (sucatalog, err)
|
||||
except ReplicationError as err:
|
||||
print('Could not replicate %s: %s' % (sucatalog, err), file=sys.stderr)
|
||||
exit(-1)
|
||||
if os.path.splitext(localcatalogpath)[1] == '.gz':
|
||||
with gzip.open(localcatalogpath) as the_file:
|
||||
@ -308,17 +320,19 @@ def download_and_parse_sucatalog(sucatalog, workdir, ignore_cache=False):
|
||||
try:
|
||||
catalog = plistlib.readPlistFromString(content)
|
||||
return catalog
|
||||
except ExpatError, err:
|
||||
print >> sys.stderr, (
|
||||
'Error reading %s: %s' % (localcatalogpath, err))
|
||||
except ExpatError as err:
|
||||
print(
|
||||
'Error reading %s: %s' % (localcatalogpath, err),
|
||||
file=sys.stderr)
|
||||
exit(-1)
|
||||
else:
|
||||
try:
|
||||
catalog = plistlib.readPlist(localcatalogpath)
|
||||
return catalog
|
||||
except (OSError, IOError, ExpatError), err:
|
||||
print >> sys.stderr, (
|
||||
'Error reading %s: %s' % (localcatalogpath, err))
|
||||
except (OSError, IOError, ExpatError) as err:
|
||||
print(
|
||||
'Error reading %s: %s' % (localcatalogpath, err),
|
||||
file=sys.stderr)
|
||||
exit(-1)
|
||||
|
||||
|
||||
@ -355,8 +369,9 @@ def os_installer_product_info(catalog, workdir, ignore_cache=False):
|
||||
try:
|
||||
dist_path = replicate_url(
|
||||
dist_url, root_dir=workdir, ignore_cache=ignore_cache)
|
||||
except ReplicationError, err:
|
||||
print >> sys.stderr, 'Could not replicate %s: %s' % (dist_url, err)
|
||||
except ReplicationError as err:
|
||||
print(
|
||||
'Could not replicate %s: %s' % (dist_url, err), file=sys.stderr)
|
||||
dist_info = parse_dist(dist_path)
|
||||
product_info[product_key]['DistributionPath'] = dist_path
|
||||
product_info[product_key].update(dist_info)
|
||||
@ -377,18 +392,19 @@ def replicate_product(catalog, product_id, workdir, ignore_cache=False):
|
||||
package['URL'], root_dir=workdir,
|
||||
show_progress=True, ignore_cache=ignore_cache,
|
||||
attempt_resume=(not ignore_cache))
|
||||
except ReplicationError, err:
|
||||
print >> sys.stderr, (
|
||||
'Could not replicate %s: %s' % (package['URL'], err))
|
||||
except ReplicationError as err:
|
||||
print(
|
||||
'Could not replicate %s: %s' % (package['URL'], err),
|
||||
file=sys.stderr)
|
||||
exit(-1)
|
||||
if 'MetadataURL' in package:
|
||||
try:
|
||||
replicate_url(package['MetadataURL'], root_dir=workdir,
|
||||
ignore_cache=ignore_cache)
|
||||
except ReplicationError, err:
|
||||
print >> sys.stderr, (
|
||||
except ReplicationError as err:
|
||||
print((
|
||||
'Could not replicate %s: %s'
|
||||
% (package['MetadataURL'], err))
|
||||
% (package['MetadataURL'], err)), file=sys.stderr)
|
||||
exit(-1)
|
||||
|
||||
|
||||
@ -437,18 +453,19 @@ def main():
|
||||
elif args.seedprogram:
|
||||
su_catalog_url = get_seed_catalog(args.seedprogram)
|
||||
if not su_catalog_url:
|
||||
print >> sys.stderr, (
|
||||
print((
|
||||
'Could not find a catalog url for seed program %s'
|
||||
% args.seedprogram)
|
||||
print >> sys.stderr, (
|
||||
% args.seedprogram), file=sys.stderr)
|
||||
print((
|
||||
'Valid seeding programs are: %s'
|
||||
% ', '.join(get_seeding_programs()))
|
||||
% ', '.join(get_seeding_programs())), file=sys.stderr)
|
||||
exit(-1)
|
||||
else:
|
||||
su_catalog_url = get_default_catalog()
|
||||
if not su_catalog_url:
|
||||
print >> sys.stderr, (
|
||||
'Could not find a default catalog url for this OS version.')
|
||||
print(
|
||||
'Could not find a default catalog url for this OS version.',
|
||||
file=sys.stderr)
|
||||
exit(-1)
|
||||
|
||||
# download sucatalog and look for products that are for macOS installers
|
||||
@ -458,32 +475,33 @@ def main():
|
||||
catalog, args.workdir, ignore_cache=args.ignore_cache)
|
||||
|
||||
if not product_info:
|
||||
print >> sys.stderr, (
|
||||
'No macOS installer products found in the sucatalog.')
|
||||
print(
|
||||
'No macOS installer products found in the sucatalog.',
|
||||
file=sys.stderr)
|
||||
exit(-1)
|
||||
|
||||
# display a menu of choices (some seed catalogs have multiple installers)
|
||||
print '%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version',
|
||||
'Build', 'Post Date', 'Title')
|
||||
print('%2s %12s %10s %8s %11s %s' % ('#', 'ProductID', 'Version',
|
||||
'Build', 'Post Date', 'Title'))
|
||||
for index, product_id in enumerate(product_info):
|
||||
print '%2s %12s %10s %8s %11s %s' % (
|
||||
print('%2s %12s %10s %8s %11s %s' % (
|
||||
index + 1,
|
||||
product_id,
|
||||
product_info[product_id]['version'],
|
||||
product_info[product_id]['BUILD'],
|
||||
product_info[product_id]['PostDate'].strftime('%Y-%m-%d'),
|
||||
product_info[product_id]['title']
|
||||
)
|
||||
))
|
||||
|
||||
answer = raw_input(
|
||||
answer = input(
|
||||
'\nChoose a product to download (1-%s): ' % len(product_info))
|
||||
try:
|
||||
index = int(answer) - 1
|
||||
if index < 0:
|
||||
raise ValueError
|
||||
product_id = product_info.keys()[index]
|
||||
product_id = list(product_info.keys())[index]
|
||||
except (ValueError, IndexError):
|
||||
print 'Exiting.'
|
||||
print('Exiting.')
|
||||
exit(0)
|
||||
|
||||
# download all the packages for the selected product
|
||||
@ -499,7 +517,7 @@ def main():
|
||||
os.unlink(sparse_diskimage_path)
|
||||
|
||||
# make an empty sparseimage and mount it
|
||||
print 'Making empty sparseimage...'
|
||||
print('Making empty sparseimage...')
|
||||
sparse_diskimage_path = make_sparse_image(volname, sparse_diskimage_path)
|
||||
mountpoint = mountdmg(sparse_diskimage_path)
|
||||
if mountpoint:
|
||||
@ -508,7 +526,7 @@ def main():
|
||||
product_info[product_id]['DistributionPath'],
|
||||
mountpoint)
|
||||
if not success:
|
||||
print >> sys.stderr, 'Product installation failed.'
|
||||
print('Product installation failed.', file=sys.stderr)
|
||||
unmountdmg(mountpoint)
|
||||
exit(-1)
|
||||
# add the seeding program xattr to the app if applicable
|
||||
@ -517,7 +535,7 @@ def main():
|
||||
installer_app = find_installer_app(mountpoint)
|
||||
if installer_app:
|
||||
xattr.setxattr(installer_app, 'SeedProgram', seeding_program)
|
||||
print 'Product downloaded and installed to %s' % sparse_diskimage_path
|
||||
print('Product downloaded and installed to %s' % sparse_diskimage_path)
|
||||
if args.raw:
|
||||
unmountdmg(mountpoint)
|
||||
else:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user