13. August 2015
Update 27th Aug, 2015: For easier management of Xcode Server from the command line, I created a tool called xcskarel!
Welcome to the first Xcode Server Hack! As you might know, Xcode Server Tutorials are aimed at step by step instructions for Xcode Server users. However, I also needed a format in which to write these (mostly more advanced) random hacks and debugging tips I’ve collected along the way. This is what the Xcode Server Hacks series is for! These articles will to be shorter and always focused on just a single issue. Today? We’ll look at how to get OS X Server (the app I told you you need to get Xcode Server running) out of the mix and control Xcode Server purely from the command line.
In Under the Hood I’ve talked at length about how Xcode Server is structured internally. You might remember that you can find one important binary in your Xcode bundle at
You can run this tool directly from the above path, like we will for the rest of this article, but please note that there’s a much easier way to call
xcscontrol: and that is with the mighty
xcrun finds a tool in your Xcode bundle and triggers it, so that you don’t need to know where it sits on disk (and also so that Xcode doesn’t have to copy all its tools to
/usr/bin, which would make multi-Xcode setup very difficult). Which Xcode bundle, you might ask, since many of us run multiple versions of Xcode at the same time (I have 4 right now).
xcrun uses the Xcode which is selected globally by
$ xcode-select -p right now and if your Xcode is at least Xcode 7 Beta 6 (
/Applications/Xcode-beta6.app/...), feel free to just use
xcrun xcscontrol <COMMAND> for the rest of this article. (Thanks, @ioswes!)
xcscontrol is exactly what you’d expect based on its name - the tool you use to manage Xcode Server on your machine. Its help page tells us
$ /Applications/Xcode-beta5.app/Contents/Developer/usr/bin/xcscontrol --help xcscontrol must be run as root, exiting
… that it must be ran as
root. Hmm, why is that? Well, the fact is that “Xcode Server” is just an umbrella term for a whole set of users and tools running on your system. And the build stage is actually managed by a separate user called
_xcsbuildd (more about it in Tutorial 3). And in order to create users, add launch daemons, control
xcode-select and more, it needs to have root privileges. So there,
sudo it is.
$ sudo /Applications/Xcode-beta6.app/Contents/Developer/usr/bin/xcscontrol --help Usage: xcscontrol [options] -h, --help, --usage Prints usage information for xcscontrol --version Displays version information --preflight Preflights the Xcode Server service --initialize Initializes Xcode Server, must be called before the service is usable --reset Resets Xcode Server, removing all service data and stopping all services --restart Restart Xcode Server without removing any service data --shutdown Stops Xcode Server --health Fetches and displays server status and statistics --fix-permissions Sets permissions on the shared data directory to their defaults --list-portal-teams APPLEID PASSWORD Lists valid ADC teams for a given Apple ID and password --join-portal-team TEAMID APPLEID PASSWORD Joins a given ADC team with a supplied Apple ID and password --verify-portal-support TEAMID APPLEID PASSWORD Tests ADC support for a given team, Apple ID and password --sync-portal Syncs all teams with the ADC portal --sync-portal-team TEAMID Syncs a specific team with the ADC portal --add-device-to-team DEVICEUDID DEVICENAME TEAMID Adds a device with a given UDID and name to ADC team with given ID --list-devices Lists all known (physical) devices for testing --list-simulators Lists all known simulators for testing --set-device-development-bit DEVICEUDID Configures a device with a given UDID for development --configure-email-from-name DISPLAY NAME Configures a display name to be used in the from field when delivering email notifications --configure-email-from-address ADDRESS Configures an address to be used in the from field when delivering email notifications --configure-email-reply-to-name DISPLAY NAME Configures a display name to be used in the reply-to field when delivering email notifications --configure-email-reply-to-address ADDRESS Configures an address to be used in the reply-to field when delivering email notifications --configure-email-transport RELAY Configures an SMTP relay for delivering email notifications
So much fun to be had! Let’s dig in.
First, let’s clean our existing Xcode Server instance. We’ll remove all Xcode Server data we have locally (so don’t do this if you’re on your production machine and still need your Bots and Integrations) with the
This is the ultimate distructive command. It nukes the
/Library/Developer/XcodeServer folder and stops all XCS’s daemons. It’s tremendously useful when you mess something up while hacking XCS, so keep this one bookmarked.
$ sudo /Applications/Xcode-beta6.app/Contents/Developer/usr/bin/xcscontrol --reset
OK, now our machine doesn’t have any idea that Xcode Server was ever running on it.
Let’s start our Xcode Server instance, shall we? The command to do that is called
initialize, but it might as well be called
$ sudo /Applications/Xcode-beta6.app/Contents/Developer/usr/bin/xcscontrol --initialize *** (1/25) [START] Verifying that this Xcode Server is supported (1/25) [END - 0.07s] Verifying that this Xcode Server is supported *** (2/25) [START] Creating default data directories (if they are missing) *** (3/25) [START] Enabling developer mode if required *** (4/25) [START] Running xcode-select --switch for /Applications/Xcode-beta6.app (2/25) [END - 0.01s] Creating default data directories (if they are missing) *** (5/25) [START] Configuring SSL infrastructure *** (6/25) [START] Creating a symlink to the current Xcode application path (6/25) [END - 0.00s] Creating a symlink to the current Xcode application path *** (7/25) [START] Configuring default hosted HTTP repository access settings (7/25) [END - 0.00s] Configuring default hosted HTTP repository access settings *** (8/25) [START] Configuring default hosted SSH repository access settings (8/25) [END - 0.00s] Configuring default hosted SSH repository access settings *** (9/25) [START] Setting up the config file for Redis *** (10/25) [START] Setting up the config file for CouchDB *** (11/25) [START] Configuring system launchd jobs (9/25) [END - 0.00s] Setting up the config file for Redis (10/25) [END - 0.00s] Setting up the config file for CouchDB (11/25) [END - 0.01s] Configuring system launchd jobs *** (12/25) [START] Configuring CouchDB to use all cores *** (13/25) [START] Configuring log rolling (12/25) [END - 0.00s] Configuring CouchDB to use all cores *** (14/25) [START] Creating group for service users if necessary (13/25) [END - 0.00s] Configuring log rolling (14/25) [END - 0.01s] Creating group for service users if necessary (3/25) [END - 0.07s] Enabling developer mode if required (4/25) [END - 0.08s] Running xcode-select --switch for /Applications/Xcode-beta6.app (5/25) [END - 6.64s] Configuring SSL infrastructure *** (15/25) [START] Creating service users if necessary (15/25) [END - 4.22s] Creating service users if necessary *** (16/25) [START] Fixing filesystem permissions (16/25) [END - 0.02s] Fixing filesystem permissions *** (17/25) [START] Starting CouchDB *** (18/25) [START] Starting Redis (18/25) [END - 0.14s] Starting Redis (17/25) [END - 1.14s] Starting CouchDB *** (19/25) [START] Initializing database (19/25) [END - 0.19s] Initializing database *** (20/25) [START] Starting API server (20/25) [END - 3.14s] Starting API server *** (21/25) [START] Saving version information *** (22/25) [START] Starting nginx daemon (22/25) [END - 0.07s] Starting nginx daemon *** (23/25) [START] Starting control daemon (23/25) [END - 0.07s] Starting control daemon (21/25) [END - 0.44s] Saving version information *** (24/25) [START] Writing initialization receipt (24/25) [END - 0.00s] Writing initialization receipt *** (25/25) [START] Upgrading Xcode Server data *** (25/25) [IN PROGRESS] Upgrading Xcode Server data (0%) (25/25) [END - 5.58s] Upgrading Xcode Server data Succeeded! Total time: 21.46 seconds
I always wondered why the initialization takes ages (read: tens of seconds) when triggered from OS X Server. Now we know - XCS needs to perform a bunch of actions, like creating a bunch of folders, spinning up CouchDB and Redis, configuring SSH keys, setting up logging, running
xcode-select to switch your active Xcode, creating that famous
_xcsbuildd user, spinning up its Node.js API server and a Nginx proxy… So 25 seconds is actually not that bad 😇.
Starting with Xcode 7 Beta 6, you also have to run
--preflight after running
--initialize to have Xcode Server be setup correctly.
This is the equivalent of turning the huge green toggle off and on in OS X Server. This is useful when you start modifying files in
to e.g. print out what the API server has received (when reverse engineering the XCS <> Xcode communication), because these files are only loaded on XCS’s startup. By using
restart you can force XCS to reload these files.
When you want to upgrade your Xcode to a new version, I’d suggest you shut XCS down first. This is like turning the big toggle off. Shutdown turns Xcode Server off, but doesn’t remove all its data like
--reset does. I also shut down my Xcode Server when I’m waiting for an OS X update to finish, otherwise it won’t automatically restart, because “other users are logged-in” (oh hey,
That’s it for today! I just wanted to show you how to run Xcode Server without ever touching OS X Server. I don’t recommend you avoid using OS X Server altogether, because it still provides an easier way to manage Xcode Server. It’s just nice to know that you can do this.
There are plenty of commands in
xcscontrol that I didn’t cover and some of them are pretty powerful. This is your homework. Look at them and see if you can map each command to a UI element in OS X Server.
- Aug 25th, 2015: updated for Xcode 7 Beta 6, added section about preflight
I hope you found this useful or interesting. For criticism, praise and future articles, I’m @czechboy0 on Twitter.