Xcode Server Hacks: 2. Taking control with xcscontrol

This article is Part 2 of a whole series called Xcode Server Hacks. Check out Part 1 first if you haven't already. New posts are always tweeted by me (@czechboy0.dev) on Bluesky.


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

/Applications/Xcode-beta6.app/Contents/Developer/usr/bin/xcscontrol

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 super-command. 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. Run $ 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

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.

reset

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 --reset command.

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.

initialize

Let’s start our Xcode Server instance, shall we? The command to do that is called initialize, but it might as well be called start.

$ 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 😇.

preflight

Starting with Xcode 7 Beta 6, you also have to run --preflight after running --initialize to have Xcode Server be setup correctly.

restart

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

/Applications/Xcode-beta6.app/Contents/Developer/usr/share/xcs/xcsd/

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.

shutdown

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, _xcsbuildd).

what else?

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.

updates

  • 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.dev on Bluesky.