diff --git a/README.md b/README.md index eccf628..38ab62e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,9 @@ Some scripts that might be of use to macOS admins. Might be related to Munki; might not. -These are only supported using Apple's Python on macOS. There is no support for running these on Windows or Linux. +These are currently only supported using Apple's Python on macOS. There is no support for running these on Windows or Linux. + +In macOS 12.3, Apple will be removing its Python 2.7 install. You'll need to provide your own Python to use these scripts. You may also need to install additional Python modules. #### getmacosipsws.py @@ -13,7 +15,7 @@ Quick-and-dirty tool to download the macOS IPSW files currently advertised by Ap This script can create disk images containing macOS Installer applications available via Apple's softwareupdate catalogs. -Run `./installinstallmacos.py --help` to see the available options. +Run `python ./installinstallmacos.py --help` to see the available options. The tool assembles "Install macOS" applications by downloading the packages from Apple's softwareupdate servers and then installing them into a new empty disk image. @@ -39,38 +41,8 @@ Use a compatible Mac or select a different build compatible with your current ha ##### Important note for Catalina+ Catalina privacy protections might interfere with the operation of this tool if you run it from ~/Desktop, ~/Documents, ~/Downloads or other directories protected in Catalina. Consider using /Users/Shared (or subdirectory) as the "working space" for this tool. -##### October 2020 update -In late September, Apple made some changes to how their servers returned requested results. This led to `installinstallmacos.py` complaining about invalid XML and listing macOS installers with UNKNOWN build numbers, among other issues. -If you encounter these issues: - - Update to the current version of the script - - Run it at least once with the `--ignore-cache` option, or remove the content directory to remove corrupted files in the content cache. ##### Alternate implementations Graham Pugh has a fork with a lot more features and bells and whistles. Check it out if your needs aren't met by this tool. https://github.com/grahampugh/macadmin-scripts ------ - -### Legacy scripts no longer in development or supported - -#### createbootvolfromautonbi.py - -(This tool has not been tested/updated since before 10.14 shipped. It may not work as expected with current versions of macOS. There are currently no plans to update it.) - -A tool to make bootable disk volumes from the output of autonbi. Especially -useful to make bootable disks containing Imagr and the 'SIP-ignoring' kernel, -which allows Imagr to run scripts that affect SIP state, set UAKEL options, and -run the `startosinstall` component, all of which might otherwise require network -booting from a NetInstall-style nbi. - -This provides a way to create a bootable external disk that acts like the Netboot environment used by/needed by Imagr. - -This command converts the output of Imagr's `make nbi` into a bootable external USB disk: -`sudo ./createbootvolfromautonbi.py --nbi ~/Desktop/10.13.6_Imagr.nbi --volume /Volumes/ExternalDisk` - - -#### make_firmwareupdater_pkg.sh - -This script was used to extract the firmware updaters from early High Sierra installers and make a standalone installer package that could be used to upgrade Mac firmware before installing High Sierra via imaging. - -Later High Sierra installer changes have broken this script; since installing High Sierra via imaging is not recommended or supported by Apple and several other alternatives are now available, I don't plan on attempting to fix or upgrade this tool. diff --git a/createbootvolfromautonbi.py b/createbootvolfromautonbi.py deleted file mode 100755 index 021d4b0..0000000 --- a/createbootvolfromautonbi.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/python -# encoding: utf-8 -# -# Copyright 2017 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. -'''A tool to make bootable disk volumes from the output of autonbi. Especially -useful to make bootable disks containing Imagr and the 'SIP-ignoring' kernel, -which allows Imagr to run scripts that affect SIP state, set UAKEL options, and -run the `startosinstall` component, all of which might otherwise require network -booting from a NetInstall-style nbi.''' - -import argparse -import os -import plistlib -import subprocess -import sys -import urlparse - - -# dmg helpers -def mountdmg(dmgpath): - """ - Attempts to mount the dmg at dmgpath and returns first mountpoint - """ - mountpoints = [] - dmgname = os.path.basename(dmgpath) - cmd = ['/usr/bin/hdiutil', 'attach', dmgpath, - '-mountRandom', '/tmp', '-nobrowse', '-plist', - '-owners', 'on'] - proc = subprocess.Popen(cmd, bufsize=-1, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (pliststr, err) = proc.communicate() - if proc.returncode: - print >> sys.stderr, 'Error: "%s" while mounting %s.' % (err, dmgname) - return None - if pliststr: - plist = plistlib.readPlistFromString(pliststr) - for entity in plist['system-entities']: - if 'mount-point' in entity: - mountpoints.append(entity['mount-point']) - - return mountpoints[0] - - -def unmountdmg(mountpoint): - """ - Unmounts the dmg at mountpoint - """ - proc = subprocess.Popen(['/usr/bin/hdiutil', 'detach', mountpoint], - bufsize=-1, stdout=subprocess.PIPE, - 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 - # try forcing the unmount - retcode = subprocess.call(['/usr/bin/hdiutil', 'detach', mountpoint, - '-force']) - if retcode: - print >> sys.stderr, 'Failed to unmount %s' % mountpoint - - -def locate_basesystem_dmg(nbi): - '''Finds and returns the relative path to the BaseSystem.dmg within the - NetInstall.dmg''' - source_boot_plist = os.path.join(nbi, 'i386/com.apple.Boot.plist') - try: - boot_args = plistlib.readPlist(source_boot_plist) - except Exception, err: - print >> sys.stderr, err - sys.exit(-1) - kernel_flags = boot_args.get('Kernel Flags') - if not kernel_flags: - print >> sys.stderr, 'i386/com.apple.Boot.plist is missing Kernel Flags' - sys.exit(-1) - # kernel flags should in the form 'root-dmg=file:///path' - if not kernel_flags.startswith('root-dmg='): - print >> sys.stderr, 'Unexpected Kernel Flags: %s' % kernel_flags - sys.exit(-1) - file_url = kernel_flags[9:] - dmg_path = urlparse.unquote(urlparse.urlparse(file_url).path) - # return path minus leading slash - return dmg_path.lstrip('/') - - -def copy_system_version_plist(nbi, target_volume): - '''Copies System/Library/CoreServices/SystemVersion.plist from the - BaseSystem.dmg to the target volume.''' - netinstall_dmg = os.path.join(nbi, 'NetInstall.dmg') - if not os.path.exists(netinstall_dmg): - print >> sys.stderr, "Missing NetInstall.dmg from nbi folder" - sys.exit(-1) - print 'Mounting %s...' % netinstall_dmg - netinstall_mount = mountdmg(netinstall_dmg) - if not netinstall_mount: - sys.exit(-1) - basesystem_dmg = os.path.join(netinstall_mount, locate_basesystem_dmg(nbi)) - print 'Mounting %s...' % basesystem_dmg - basesystem_mount = mountdmg(basesystem_dmg) - if not basesystem_mount: - unmountdmg(netinstall_mount) - sys.exit(-1) - source = os.path.join( - basesystem_mount, 'System/Library/CoreServices/SystemVersion.plist') - dest = os.path.join( - target_volume, 'System/Library/CoreServices/SystemVersion.plist') - try: - subprocess.check_call( - ['/usr/bin/ditto', '-V', source, dest]) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err - unmountdmg(basesystem_mount) - unmountdmg(netinstall_mount) - sys.exit(-1) - - unmountdmg(basesystem_mount) - unmountdmg(netinstall_mount) - - -def copy_boot_files(nbi, target_volume): - '''Copies some boot files, yo''' - files_to_copy = [ - ['NetInstall.dmg', 'NetInstall.dmg'], - ['i386/PlatformSupport.plist', - 'System/Library/CoreServices/PlatformSupport.plist'], - ['i386/booter', 'System/Library/CoreServices/boot.efi'], - ['i386/booter', 'usr/standalone/i386/boot.efi'], - ['i386/x86_64/kernelcache', - 'System/Library/PrelinkedKernels/prelinkedkernel'] - ] - for source, dest in files_to_copy: - full_source = os.path.join(nbi, source) - full_dest = os.path.join(target_volume, dest) - try: - subprocess.check_call( - ['/usr/bin/ditto', '-V', full_source, full_dest]) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err - sys.exit(-1) - - -def make_boot_plist(nbi, target_volume): - '''Creates our com.apple.Boot.plist''' - source_boot_plist = os.path.join(nbi, 'i386/com.apple.Boot.plist') - try: - boot_args = plistlib.readPlist(source_boot_plist) - except Exception, err: - print >> sys.stderr, err - sys.exit(-1) - kernel_flags = boot_args.get('Kernel Flags') - if not kernel_flags: - print >> sys.stderr, 'i386/com.apple.Boot.plist is missing Kernel Flags' - sys.exit(-1) - # prepend the container-dmg path - boot_args['Kernel Flags'] = ( - 'container-dmg=file:///NetInstall.dmg ' + kernel_flags) - boot_plist = os.path.join( - target_volume, - 'Library/Preferences/SystemConfiguration/com.apple.Boot.plist') - plist_dir = os.path.dirname(boot_plist) - if not os.path.exists(plist_dir): - os.makedirs(plist_dir) - try: - plistlib.writePlist(boot_args, boot_plist) - except Exception, err: - print >> sys.stderr, err - sys.exit(-1) - - -def bless(target_volume, label=None): - '''Bless the target volume''' - blessfolder = os.path.join(target_volume, 'System/Library/CoreServices') - if not label: - label = os.path.basename(target_volume) - try: - subprocess.check_call( - ['/usr/sbin/bless', '--folder', blessfolder, '--label', label]) - except subprocess.CalledProcessError, err: - print >> sys.stderr, err - sys.exit(-1) - - -def main(): - '''Do the thing we were made for''' - parser = argparse.ArgumentParser() - parser.add_argument('--nbi', required=True, metavar='path_to_nbi', - help='Path to nbi folder created by autonbi.') - parser.add_argument('--volume', required=True, - metavar='path_to_disk_volume', - help='Path to disk volume.') - args = parser.parse_args() - copy_system_version_plist(args.nbi, args.volume) - copy_boot_files(args.nbi, args.volume) - make_boot_plist(args.nbi, args.volume) - bless(args.volume) - - -if __name__ == '__main__': - main() diff --git a/getmacosipsws.py b/getmacosipsws.py index 38713d8..6086416 100755 --- a/getmacosipsws.py +++ b/getmacosipsws.py @@ -1,7 +1,7 @@ -#!/usr/bin/python +#!/usr/bin/env python # encoding: utf-8 # -# Copyright 2021 Greg Neagle. +# Copyright 2021-2022 Greg Neagle. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/installinstallmacos.py b/installinstallmacos.py index 8a23916..75eb38d 100755 --- a/installinstallmacos.py +++ b/installinstallmacos.py @@ -1,7 +1,7 @@ -#!/usr/bin/python +#!/usr/bin/env python # encoding: utf-8 # -# Copyright 2017 Greg Neagle. +# Copyright 2017-2022 Greg Neagle. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -33,7 +33,6 @@ import plistlib import shutil import subprocess import sys -import xattr try: # python 2 @@ -45,6 +44,15 @@ from xml.dom import minidom from xml.parsers.expat import ExpatError from distutils.version import LooseVersion +try: + import xattr +except ImportError: + print( + "This tool requires the Python xattr module. " + "Perhaps run `pip install xattr` to install it." + ) + sys.exit(-1) + DEFAULT_SUCATALOGS = { "17": ( diff --git a/make_firmwareupdater_pkg.sh b/make_firmwareupdater_pkg.sh deleted file mode 100755 index 29dcce0..0000000 --- a/make_firmwareupdater_pkg.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh -# Based on investigations and work by Pepijn Bruienne -# Expects a single /Applications/Install macOS High Sierra*.app on disk - -# -# 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. - -IDENTIFIER="com.foo.FirmwareUpdateStandalone" -VERSION=1.0 - -# find the Install macOS High Sierra.app and mount the embedded InstallESD disk image -echo "Mounting High Sierra ESD disk image..." -/usr/bin/hdiutil mount /Applications/Install\ macOS\ High\ Sierra*.app/Contents/SharedSupport/InstallESD.dmg - -# expand the FirmwareUpdate.pkg so we can copy resources from it -echo "Expanding FirmwareUpdate.pkg" -/usr/sbin/pkgutil --expand /Volumes/InstallESD/Packages/FirmwareUpdate.pkg /tmp/FirmwareUpdate - -# we don't need the disk image any more -echo "Ejecting disk image..." -/usr/bin/hdiutil eject /Volumes/InstallESD - -# make a place to stage our pkg resources -/bin/mkdir -p /tmp/FirmwareUpdateStandalone/scripts - -# copy the needed resources -echo "Copying package resources..." -/bin/cp /tmp/FirmwareUpdate/Scripts/postinstall_actions/update /tmp/FirmwareUpdateStandalone/scripts/postinstall -# add an exit 0 at the end of the script -echo "" >> /tmp/FirmwareUpdateStandalone/scripts/postinstall -echo "" >> /tmp/FirmwareUpdateStandalone/scripts/postinstall -echo "exit 0" >> /tmp/FirmwareUpdateStandalone/scripts/postinstall -/bin/cp -R /tmp/FirmwareUpdate/Scripts/Tools /tmp/FirmwareUpdateStandalone/scripts/ - -# build the package -echo "Building standalone package..." -/usr/bin/pkgbuild --nopayload --scripts /tmp/FirmwareUpdateStandalone/scripts --identifier "$IDENTIFIER" --version "$VERSION" /tmp/FirmwareUpdateStandalone/FirmwareUpdateStandalone.pkg - -# clean up -/bin/rm -r /tmp/FirmwareUpdate -/bin/rm -r /tmp/FirmwareUpdateStandalone/scripts \ No newline at end of file