Skip to content

Instantly share code, notes, and snippets.

Created July 18, 2014 18:23
Show Gist options
  • Save anonymous/09999a8b32ea3185ca47 to your computer and use it in GitHub Desktop.
Save anonymous/09999a8b32ea3185ca47 to your computer and use it in GitHub Desktop.
#!/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