Created
July 18, 2014 18:23
-
-
Save anonymous/09999a8b32ea3185ca47 to your computer and use it in GitHub Desktop.
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/perl | |
use warnings; | |
############################################################################ | |
# Settings | |
############################################################################ | |
#--------------------------------------------------------------------------- | |
# Name of OS X installer app. | |
#--------------------------------------------------------------------------- | |
$osx_installer = "Install OS X Mavericks.app"; | |
#--------------------------------------------------------------------------- | |
# Paths where the script expects to find disks when they're mounted. | |
#--------------------------------------------------------------------------- | |
$mounted_install_esd = "/Volumes/OS X Install ESD"; | |
$mounted_base_system = "/Volumes/OS X Base System"; | |
#--------------------------------------------------------------------------- | |
# Location of Tiamo's 32-bit boot.efi. | |
# If you want verbose (text mode) boot, substitute this URL and MD5: | |
# url: http://forums.macrumors.com/attachment.php?attachmentid=448666 | |
# md5: aa787e27b8eddadf1ca4f4038a5adbd4 | |
#--------------------------------------------------------------------------- | |
$bootefi_url = "http://forums.macrumors.com/attachment.php?attachmentid=449107"; | |
$bootefi_md5 = "b7c115564ddc8fd87b6a590a0ed66c44"; | |
#--------------------------------------------------------------------------- | |
# List of Mac models and board IDs to be added while making a USB installer. | |
# | |
# Mac models and board IDs were obtained here: | |
# https://gist.github.com/pudquick/5341212 | |
# | |
# Mavericks compatibility according to: | |
# http://www.everymac.com/mac-answers/os-x-10.9-mavericks-faq/os-x-mavericks-compatible-macs-system-requirements.html | |
# | |
# To discover a Mac's product name and board ID: | |
# ioreg -lp IOService | grep product-name | |
# ioreg -lp IOService | grep board-id | |
#--------------------------------------------------------------------------- | |
$unsupported_models = { | |
# MacPro3,1 and up are officially supported. | |
"MacPro1,1" => ["Mac-F4208DC8"], | |
"MacPro2,1" => ["Mac-F4208DA9"], | |
# iMac4,1 was the first Intel iMac. iMac7,1 and up are supported. | |
"iMac4,1" => ["Mac-F42786C8"], | |
"iMac5,1" => ["Mac-F4228EC8", "Mac-F42786A9"], | |
"iMac5,2" => ["Mac-F4218EC8"], | |
"iMac6,1" => ["Mac-F4218FC8"], | |
# MacBook5,1 and up are officially supported. | |
"MacBook1,1" => ["Mac-F4208CC8"], | |
"MacBook2,1" => ["Mac-F4208CA9", "Mac-F4208CAA"], | |
"MacBook3,1" => ["Mac-F22788C8"], | |
"MacBook4,1" => ["Mac-F22788A9"], | |
# MacBookAir2,1 and up are officially supported. | |
"MacBookAir1,1" => ["Mac-F42C8CC8"], | |
# MacBookPro3,1 and up are officially supported. | |
"MacBookPro1,1" => ["Mac-F425BEC8"], | |
"MacBookPro2,1" => ["Mac-F42189C8"], | |
"MacBookPro2,2" => ["Mac-F42187C8"], | |
# Macmini3,1 and up are officially supported. | |
"Macmini1,1" => ["Mac-F4208EC8"], | |
"Macmini2,1" => ["Mac-F4208EAA"], | |
# Xserve3,1 and up are officially supported. | |
"Xserve1,1" => ["Mac-F4208AC8"], | |
"Xserve2,1" => ["Mac-F42289C8"], | |
}; | |
############################################################################ | |
# Main script - most users should not need to edit below here | |
############################################################################ | |
############################################################################ | |
# instructions | |
############################################################################ | |
print <<INSTRUCTIONS_END; | |
This script will create a Mavericks USB install key from Apple's installer | |
application, replace its boot.efi with Tiamo's 32-bit boot.efi, and remove | |
Apple's unsupported Mac lockouts. So far, best results have been obtained | |
with Mac Pro 1,1 and 2,1 using Radeon 5770 graphics cards. | |
This script automates steps 1-4 in Tiamo's post: | |
http://forums.macrumors.com/showpost.php?p=18411621&postcount=26 | |
INSTRUCTIONS_END | |
######################################################### | |
# Find the installer app | |
######################################################### | |
print "Finding OS X Mavericks installer application(s).\n"; | |
@potential_installers = `mdfind "kMDItemFSName == '$osx_installer'"`; | |
scalar(@potential_installers) > 0 or die | |
"Couldn't find a copy of the Mavericks installer!\n" . | |
"Please download it from the Mac App Store and try again.\n"; | |
chomp(@potential_installers); | |
$install_app = choose_one_from_list(@potential_installers); | |
print "Source installer app: ${install_app}\n\n"; | |
######################################################### | |
# Ask user to select a target disk. | |
######################################################### | |
my @potential_targets; | |
foreach (`/sbin/mount`) { | |
if (/^(.*) on (\/Volumes\/.*) \((.*)\)/) { | |
my $dev = $1; | |
my $mountpoint = $2; | |
my $flags = $3; | |
next unless $flags =~ /local/; | |
next unless $flags =~ /hfs/; | |
next if $flags =~ /read-only/; | |
#print "found potential target $dev , $mountpoint, $flags\n"; | |
push @potential_targets, $mountpoint; | |
} | |
} | |
scalar(@potential_targets) > 0 | |
or die "Couldn't find any target disks! Please plug in a USB disk and retry.\n"; | |
print "Please select the USB disk you wish to use as a bootable installer. This\n" . | |
"disk will be completely erased. (Hit control-C to exit instead.)\n"; | |
$asr_target = choose_one_from_list(sort @potential_targets); | |
print "Target disk: $asr_target\n"; | |
print "\n"; | |
######################################################### | |
# Download and extract boot.efi, if necessary. | |
######################################################### | |
if (! -f "/tmp/boot.efi") { | |
if (! -f "/tmp/boot.zip") { | |
print "Downloading Tiamo's boot.efi...\n"; | |
system("/usr/bin/curl -o /tmp/boot.zip '$bootefi_url'") == 0 | |
or die "boot.efi download failed!\n"; | |
print "\n"; | |
} | |
print "Unzipping boot.efi...\n"; | |
system("/usr/bin/unzip /tmp/boot.zip boot.efi -d /tmp") == 0 | |
or die "Unzip failed!\n"; | |
system("rm /tmp/boot.zip") == 0 | |
or die "Couldn't delete zipfile after extracting boot.efi!\n"; | |
} else { | |
print "Found previously downloaded boot.efi in /tmp, attempting to use.\n"; | |
} | |
print "Checksumming boot.efi...\n"; | |
if (md5("/tmp/boot.efi") ne $bootefi_md5) { | |
confirm("WARNING: boot.efi MD5 checksum doesn't match! Use it anyways?", | |
"", | |
"Cannot continue without a boot.efi\n", | |
1); | |
} else { | |
print "boot.efi MD5 checksum OK ( $bootefi_md5 )\n\n"; | |
} | |
######################################################### | |
# If there are already things mounted on the key paths, perhaps | |
# from a failed previous attempt, try to unmount and bail if we can't. | |
######################################################### | |
if (-d $mounted_install_esd) { | |
print "Attempting to unmount ${mounted_install_esd}...\n"; | |
system("/usr/bin/hdiutil detach '${mounted_install_esd}' -quiet") == 0 | |
or die "Failed. Please unmount '${mounted_install_esd}' manually and try again.\n"; | |
} | |
if (-d $mounted_base_system) { | |
print "Attempting to unmount ${mounted_base_system}...\n"; | |
system("/usr/sbin/diskutil unmount '${mounted_base_system}'") == 0 | |
or die "Failed. Please unmount '${mounted_base_system}' manually and try again.\n"; | |
} | |
######################################################### | |
# Mount InstallESD.dmg | |
######################################################### | |
$installesd_dmg = "${install_app}/Contents/SharedSupport/InstallESD.dmg"; | |
print "Mounting ${installesd_dmg}...\n"; | |
system("/usr/bin/hdiutil attach '${installesd_dmg}' -readonly -noverify -nobrowse -owners on -quiet") == 0 | |
or die "Couldn't mount ${installesd_dmg}!\n"; | |
######################################################### | |
# Use ASR to write BaseSystem.dmg (from InstallESD.dmg) to a disk. | |
######################################################### | |
$asr_source = "${mounted_install_esd}/BaseSystem.dmg"; | |
confirm("\nWARNING: About to erase ${asr_target}! Continue?", | |
"Restoring ${asr_source} to ${asr_target}\n\n", | |
"Exiting.\n", | |
1); | |
system("asr restore --source '${asr_source}' --target '${asr_target}' --erase --noprompt") == 0 | |
or die "\n\nCouldn't create USB installer! Did you remember to use sudo to run the script?\n"; | |
# prevent Spotlight indexing | |
system("touch '${mounted_base_system}/.metadata_never_index'") == 0 | |
or die "Couldn't create ${mounted_base_system}/.metadata_never_index\n"; | |
system("sleep 1 ; osascript -e 'tell application \"Terminal\" to activate'"); | |
######################################################### | |
# Copy packages into place. | |
######################################################### | |
print "\nCopying packages to bootable installer; this may take a while.\n"; | |
system("rm -f '${mounted_base_system}/System/Installation/Packages'") == 0 | |
or die "Couldn't remove Packages symlink on base system.\n"; | |
system("cp -a '${mounted_install_esd}/Packages' '${mounted_base_system}/System/Installation'") == 0 | |
or die "Couldn't copy packages from InstallESD to base system.\n"; | |
# unmount InstallESD | |
print "Unmounting ${mounted_install_esd}...\n"; | |
system("/usr/bin/hdiutil detach '${mounted_install_esd}' -quiet") == 0 | |
or warn "Warning: could not unmount '${mounted_install_esd}'\n"; | |
######################################################### | |
# Replace boot.efi in two places. | |
######################################################### | |
replace_file("${mounted_base_system}/System/Library/CoreServices/boot.efi", "/tmp/boot.efi"); | |
replace_file("${mounted_base_system}/usr/standalone/i386/boot.efi", "/tmp/boot.efi"); | |
######################################################### | |
# Patch the "Distribution" file inside OSInstall.mpkg to add more board IDs | |
# | |
# Note: should be possible to do this the cheap way by just making the | |
# function always return true, but since we have board IDs we might as well | |
# do it the "right" way. If there are reports of particular machines which | |
# absolutely won't work we can then just remove them from the list. | |
######################################################### | |
print "Patching OSInstall.mpkg...\n"; | |
$wd = "${mounted_base_system}/System/Installation/Packages"; | |
# expand package and move original file aside | |
system("pkgutil --expand '${wd}/OSInstall.mpkg' '${wd}/OSInstall.mpkg.expanded'") == 0 | |
or die "Couldn't expand ${wd}/OSInstall.mpkg\n"; | |
system("/bin/mv '${wd}/OSInstall.mpkg.expanded/Distribution' '${wd}/OSInstall.mpkg.expanded/Distribution.original'") == 0 | |
or die "Couldn't move aside ${wd}/OSInstall.mpkg.expanded/Distribution\n"; | |
# modify | |
open(DIST_ORIG, "<", "${wd}/OSInstall.mpkg.expanded/Distribution.original") | |
or die "Couldn't open ${wd}/OSInstall.mpkg.expanded/Distribution.original\n"; | |
open(DIST_MODIFIED, ">", "${wd}/OSInstall.mpkg.expanded/Distribution") | |
or die "Couldn't create ${wd}/OSInstall.mpkg.expanded/Distribution\n"; | |
foreach my $line (<DIST_ORIG>) { | |
if ($line =~ /^(\s*var\s*platformSupportValues\s*=\s*\[)(.*)/) { | |
print DIST_MODIFIED $1; | |
foreach $model(sort keys %{$unsupported_models}) { | |
foreach $board_id (@{$unsupported_models->{$model}}) { | |
print DIST_MODIFIED "\"$board_id\","; | |
} | |
} | |
print DIST_MODIFIED "$2\n"; | |
} else { | |
print DIST_MODIFIED $line; | |
} | |
} | |
close(DIST_ORIG); | |
close(DIST_MODIFIED); | |
# clean up and flatten package | |
system("rm '${wd}/OSInstall.mpkg.expanded/Distribution.original'") == 0 | |
or die "Couldn't delete ${wd}/OSInstall.mpkg.expanded/Distribution.original\n"; | |
system("pkgutil --flatten '${wd}/OSInstall.mpkg.expanded' '${wd}/OSInstall.mpkg'") == 0 | |
or die "Couldn't flatten ${wd}/OSInstall.mpkg.expanded\n"; | |
system("rm -r '${wd}/OSInstall.mpkg.expanded'") == 0 | |
or die "Couldn't delete ${wd}/OSInstall.mpkg.expanded\n"; | |
######################################################### | |
# Patch InstallableMachines.plist and PlatformSupport.plist | |
######################################################### | |
print "Patching InstallableMachines.plist...\n"; | |
$wd = "${mounted_base_system}/System/Installation/Packages"; | |
add_board_ids_to_plist("${wd}/InstallableMachines.plist"); | |
system("plutil -convert xml1 '${wd}/InstallableMachines.plist'") == 0 | |
or die "Couldn't convert ${wd}/InstallableMachines.plist back to XML\n"; | |
print "Patching PlatformSupport.plist copy #1...\n"; | |
$wd = "${mounted_base_system}/System/Library/CoreServices"; | |
add_board_ids_to_plist("${wd}/PlatformSupport.plist"); | |
add_models_to_plist("${wd}/PlatformSupport.plist"); | |
system("plutil -convert xml1 '${wd}/PlatformSupport.plist'") == 0 | |
or die "Couldn't convert ${wd}/PlatformSupport.plist back to XML\n"; | |
print "Patching PlatformSupport.plist copy #2...\n"; | |
$wd = "${mounted_base_system}/System/Library/CoreServices/com.apple.recovery.boot"; | |
add_board_ids_to_plist("${wd}/PlatformSupport.plist"); | |
add_models_to_plist("${wd}/PlatformSupport.plist"); | |
system("plutil -convert xml1 '${wd}/PlatformSupport.plist'") == 0 | |
or die "Couldn't convert ${wd}/PlatformSupport.plist back to XML\n"; | |
######################################################### | |
# Final cleanup | |
######################################################### | |
# Hide stuff that's hidden on a normal Mavericks installer | |
@hidden_items = qw(Volumes bin dev etc private sbin tmp usr var); | |
foreach $item(@hidden_items) { | |
system("chflags -h hidden '${mounted_base_system}/${item}'") == 0 | |
or die "Couldn't hide ${mounted_base_system}/${item}\n"; | |
} | |
# copy self to installer | |
system("cp '${0}' '${mounted_base_system}'") == 0 | |
or die "Couldn't copy self to ${mounted_base_system}\n"; | |
system("cp '/tmp/boot.efi' '${mounted_base_system}/tmp'") == 0 | |
or die "Couldn't copy boot.efi to ${mounted_base_system}\n"; | |
# add volume icon | |
system("cp '${install_app}/Contents/Resources/InstallAssistant.icns' '${mounted_base_system}/.VolumeIcon.icns'") == 0 | |
or die "Couldn't copy icon to ${mounted_base_system}\n"; | |
# rename volume to the normal name for an installer key | |
system("diskutil rename 'OS X Base System' 'Install OS X Mavericks'") == 0 | |
or die "Couldn't rename installer disk to 'Install OS X Mavericks'\n"; | |
######################################################### | |
# add_board_ids_to_plist(), add_models_to_plist() | |
# Patch a plist by adding board IDs or models to it | |
######################################################### | |
sub add_board_ids_to_plist { | |
my $pl = shift; | |
foreach $model(sort keys %{$unsupported_models}) { | |
foreach $board_id (@{$unsupported_models->{$model}}) { | |
system("/usr/bin/defaults write '${pl}' SupportedBoardIds -array-add '${board_id}'") == 0 | |
or die "Failed to add ${board_id} to ${pl}\n"; | |
} | |
} | |
} | |
sub add_models_to_plist { | |
my $pl = shift; | |
foreach $model(sort keys %{$unsupported_models}) { | |
system("/usr/bin/defaults write '${pl}' SupportedModelProperties -array-add '${model}'") == 0 | |
or die "Failed to add ${model} to ${pl}\n"; | |
} | |
} | |
######################################################### | |
# confirm() - Ask the user Y/N questions | |
######################################################### | |
sub confirm { | |
my $query = shift; | |
my $success = shift; | |
my $failure = shift; | |
my $exit_on_failure = shift; | |
my $r; | |
do { | |
print $query . " (y/n): "; | |
chomp ($r = <STDIN>); | |
$r = lc substr($r, 0, 1); | |
if ($r eq "y") { | |
print $success; | |
return 1; | |
} elsif ($r eq "n") { | |
print $failure; | |
return 0 unless $exit_on_failure; | |
clean_exit(); | |
} | |
} while (1); | |
} | |
######################################################### | |
# choose_one_from_list() - Ask the user to make one choice from a list. | |
######################################################### | |
sub choose_one_from_list { | |
my @items = qw(sentinel); | |
push @items, @_; | |
my $last = scalar(@items) - 1; | |
#return $items[1] if $last == 1; | |
my $i; | |
while (1) { | |
foreach $i (1..$last) { | |
print " $i : $items[$i]\n"; | |
} | |
print "Choice (enter a number, 1 to $last): "; | |
chomp ($i = <STDIN>); | |
last if ($i =~ /^\d+\z/ && $i > 0 && $i <= $last); | |
print "That isn't a valid choice. Please try again.\n"; | |
} | |
return $items[$i]; | |
} | |
######################################################### | |
# md5() - Returns the md5 checksum of a file | |
######################################################### | |
sub md5 { | |
my $file = shift; | |
my $md5_output = `/sbin/md5 '$file'`; | |
$md5_output =~ / (\w+)$/; | |
return $1; | |
} | |
######################################################### | |
# replace_file() - Replaces one file with another, leaving the | |
# original intact but renamed with ".orig" on the end. | |
######################################################### | |
sub replace_file { | |
my $original = shift; | |
my $replacement = shift; | |
print "Replacing: ${original}\n"; | |
print "with: ${replacement}\n"; | |
die "${original} doesn't exist!\n" unless (-f $original); | |
die "${replacement} doesn't exist!\n" unless (-f $replacement); | |
system("chflags nouchg '${original}'") == 0 or die "Couldn't unlock ${original}\n"; | |
system("/bin/mv '${original}' '${original}.original'") == 0 or die "Couldn't move ${original} aside\n"; | |
system("cp '${replacement}' '${original}'") == 0 or die "Couldn't copy ${replacement} into place\n"; | |
system("chmod 644 '${original}'") == 0 or die "Couldn't set permissions on ${original}\n"; | |
system("chflags uchg '${original}'") == 0 or die "Couldn't lock ${original}\n"; | |
} | |
######################################################### | |
# clean_exit() - Performs any required cleanup and exits. | |
######################################################### | |
sub clean_exit { | |
if (-d $mounted_install_esd) { | |
system("hdiutil detach '$mounted_install_esd' -quiet"); | |
} | |
exit; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment