Handling Xcode for non administrators

I work in a high security environment and we do not give anyone administrator privileges. Nobody can run sudo. This makes it difficult for developers installing and managing tools such as Xcode. Solutions had to be found for installing Xcode, installing the extras and managing multiple versions. You may have also discovered, as I did, that uploading an 8GB package to the Jamf Cloud is difficult, let alone the time it takes to package it.

So it turns out developers are perfectly capable of downloading the xip file. They also appreciate the better feedback than the total lack of it with the Jamf install process. We just need to install it for them.

Installing Xcode

Here’s my cut at it.

#!/bin/bash

# xcodeInstall.sh v2.3
# hacked this out of the old post install script. It assumes you have downloaded and unpacked Xcode in your Downloads folder.
#
#
# 23 Sept 2019 - Tony Williams
# clean up script from old Casper
# v2.1 Added tool install
# v2.2 Bugs? Never bugs!
# v2.3 4/11/2019 ARW Added '-allowUntrusted' to fix problem with tighter macOS security

# Install for Xcode - based on Hunty's post install
# but not hardcoding Xcode and run postinstall (xcode installed to default - coz i don't wanna repkg :))
#
# it will check for an Xcode app in the default location
# if found it will rename it to Xcode_${version}.app
# and then:
# - accept license
# - add current user to _developer
# - enable developer mode
# - install all embedded pkgs

consoleuser="$(/usr/bin/python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; import sys; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; sys.stdout.write(username + "\n");')"

xcode="/Users/${consoleuser}/Downloads/Xcode.app"
xcode_default_path="/Applications/Xcode.app"

# here is the hack
if [ -d "${xcode}" ]
then
	/usr/local/bin/terminal-notifier -sound default -title "Xcode" \
		-contentImage /Library/Assets/logo-light.png \
		-message "Moving Xcode"
	mv "${xcode}" /Applications
else
    echo "error: couldn't find Xcode at ${xcode}"
	/usr/local/bin/terminal-notifier -sound default -title "Xcode" \
		-contentImage /Library/Assets/logo-light.png \
		-message "Xcode missing at ${xcode}"
    exit 1
fi

if [ -d "${xcode_default_path}" ]
then
    plist="${xcode_default_path}/Contents/Info.plist"
    version=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" ${plist} )
   if [ -z "${version}" ]
   then
       echo "error: couldn't get Xcode version"
	   /usr/local/bin/terminal-notifier -sound default -title "Xcode" \
		   -contentImage /Library/Assets/logo-light.png \
		   -message "Couldn't get Xcode version"
       exit 1
   fi
   # we have a version - check if there is already one present
   xcode_new_path="/Applications/Xcode_${version}.app"
   if [ -d "${xcode_new_path}" ]
   then
       echo "error: xcode already exists: ${xcode_new_path}"
	   /usr/local/bin/terminal-notifier -sound default -title "Xcode" \
		   -contentImage /Library/Assets/logo-light.png \
		   -message "Xcode already exists: ${xcode_new_path}"
       exit 1
   else
       # rename app
       mv "${xcode_default_path}" "${xcode_new_path}"
       fi
else
    echo "error: couldn't find Xcode at ${xcode_default_path}"
	/usr/local/bin/terminal-notifier -sound default -title "Xcode" \
		-contentImage /Library/Assets/logo-light.png \
		-message "Couldn't find Xcode at ${xcode_default_path}"
    exit 1
fi

# Make user member of the _developer group
/usr/sbin/dseditgroup -o edit -a ${consoleuser} -t user _developer

# Enable developer mode
/usr/sbin/DevToolsSecurity -enable

# Accept Xcode license
"${xcode_new_path}/Contents/Developer/usr/bin/xcodebuild" -license accept

# Install embedded packages
echo "installing embedded packages ..."
/usr/local/bin/terminal-notifier -sound default -title "Xcode" \
	-contentImage /Library/Assets/logo-light.png \
	-message "Installing embedded packages"

for pkg in "${xcode_new_path}/Contents/Resources/Packages/"*.pkg
do
    echo "installing: $pkg ..."
    /usr/sbin/installer -allowUntrusted -pkg "$pkg" -target /
done

echo "done!"
exit

Notice that I use terminal-notifier to give the user as much feedback as I can. Users love feedback and I like giving users what they want. terminal-notifier is small, easily installed and uses a part of the OS users are familiar with.

You can also see that I enable developer mode and add the user to _developer without checking I’ve already done it. The two commands run as fast as a check would so I may as well, it does no damage.

iOS Simulators

Now those pesky developers want to be able to install iOS simulators. For some strange reason Apple allow you to install their iOS device simulators without authenticating as an administrator but not iOS simulators. Go figure.

Here’s a little script for that :-

#!/bin/bash

# iOS_simulator.sh v1.0
# ARW 1/12/2019
# Installs a chosen iOS simulator

app=$(osascript <<EOF
set variableName to do shell script "/usr/local/lib/ruby/gems/2.6.0/bin/xcversion simulators | grep 'iOS.*not' | sort -u"
set testArray to paragraphs of the variableName
set selectedApp to {choose from list testArray}
return selectedApp
EOF
)

sim=$(echo ${app} | sed 's#\ S.*$##')

# xcversion from https://github.com/xcpretty/xcode-install
/usr/local/lib/ruby/gems/2.6.0/bin/xcversion simulators --install="${sim}"

/usr/local/bin/terminal-notifier -sound default -title "iOS installer" \
				 -contentImage /Library/Assets/logo-light.png \
				 -message "iOS simulator install complete"

A few things about this script. Notice that I use a bash heredoc to feed AppleScript into osascript. It is easier with longer bits of script and also means you can format it nice in your script.

The next is xcversion. This is from xcode-install on Github (thank you, Orta) and I’ll show you how to install it in a moment.

Notice the line sim=$(echo ${app} | sed 's#\ S.*$##'). Most people use a / between the sections of a sed command but that is not strictly necessary. You can use any character so long as you use the same character all three times. I prefer to use # as it means I don’t need to escape the / in paths and it makes the \ that escapes a character easier to spot.

Finally, this is one of the rare occasions when I use terminal-notifier at the end of the script. I do this as it takes a long time to install an iOS simulator since step one is to download a package that is about 2Gb and before we finish we will unpack and repack it before finally running installer. Putting that notification at the end is good UX.

xcode-install

Getting xcode-install on to a Mac is non-trivial in my environment. I’ll go through it in case you have problems with proxies too.

#!/bin/bash

# xcode-install-install_1.0.sh
# (as distinct from xcode_install)
# install the tool we use to install iOS simulators
#
# You need to install the 'rubyzip' package before this script runs
#
# v1.0 2019-11-29 Tony Williams (ARW)

consoleuser="$(/usr/bin/python -c 'from SystemConfiguration import SCDynamicStoreCopyConsoleUser; username = (SCDynamicStoreCopyConsoleUser(None, None, None) or [None])[0]; username = [username,""][username in [u"loginwindow", None, u""]]; print(username);')"

/usr/local/bin/terminal-notifier -sound default -title "xcode-install" \
				 -contentImage /Library/Assets/logo-light.png \
				 -message "Installing Ruby 2.6.n"
sudo -u ${consoleuser} /usr/local/bin/brew install ruby
# turn off SSL verify for gem (you may not need this - it depends how gnarly your proxies are.)
# gem will still complain at the top of the request but will do the install
sudo -u ${consoleuser} echo ":ssl_verify_mode: 0" >> /Users/${consoleuser}/.gemrc
/usr/local/bin/terminal-notifier -sound default -title "xcode-install" \
				 -contentImage /Library/Assets/logo-light.png \
				 -message "Installing rubyzip"
# for some reason we can't download rubyzip from behind our proxy so install it using our package - check if the plain `gem install xcode-install` works for you
# then delete the package 🙂
/usr/local/opt/ruby/bin/gem install /Users/Shared/rubyzip-1.3.0.gem
rm /Users/Shared/rubyzip-1.3.0.gem
# now (finally) install our tool
/usr/local/bin/terminal-notifier -sound default -title "xcode-install" \
				 -contentImage /Library/Assets/logo-light.png \
				 -message "Installing xcode-install"
/usr/local/opt/ruby/bin/gem install xcode-install

First we have to get a better version of Ruby. Homebrew to the rescue, though brew will not put it in /usr/local/bin which is why we call gem using the full path in /usr/local/opt/ruby/bin.

Then before I actually call gem I need to tell it to ignore SSL errors since our proxy gets in the way. Adding :ssl_verify_mode: to the user’s .gemrc file fixes that.

Next, for some strange reason our proxy also upsets the download of rubyzip-1.3.0 from rubygems (but no other gem. Huh?) So I turned it into a package and I install the package before I run the script. This requires using gem to install that before xcode-install.

Try doing it without the two proxy workarounds, you may have nicer proxies than me.

Finally we install xcode-install. This will take some time as it has a lot of requirements nested inside it.

xcode-select

Now we have to allow those pesky developers to choose one of the many versions of Xcode they have installed to be the “active developer directory”. We need to run xcode-select for them, this is a tiny script.

#!/bin/bash

# xcode-select.sh v1.0
# ARW 24/9/2019
# runs xcode-select on a chosen Xcode version

app=$(osascript <<EOF
set variableName to do shell script "cd /Applications ; ls -d Xcode*"
set testArray to paragraphs of the variableName
set selectedApp to {choose from list testArray}
return selectedApp
EOF
)

xcode-select --switch  "/Applications/${app}"

Oh, and since we let them install multiple versions of Xcode we better give them a tool to delete a version or they will fill their hard drives and complain about that.

#!/bin/bash

# xcode-delete.sh v1.0
# ARW 24/9/2019
# deletes a chosen Xcode version

app=$(osascript <<EOF
set variableName to do shell script "cd /Applications ; ls -d Xcode*"
set testArray to paragraphs of the variableName
set selectedApp to {choose from list testArray}
return selectedApp
EOF
)

rm -rf  "/Applications/${app}"

Notice the similarity with the xcode-select script? Hey, if it works
once.

Now we have those pesky developers covered without giving them admin rights!

If you want the code it is on Github. In fact, the code there may be newer than the above.

Before I go, remember the motto of the “Sirius Cybernetics Corporation Complaints Division”, “Share and Enjoy”.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s