Running ThoughtWorks Go Server as a Mac OS X Launch Daemon

ThoughtWorks Go is a continuous delivery system that has recently become open source and free to use. This guide will talk you through the steps necessary to get the Go Server up and running as a Mac OS X Launch Daemon so there is no need to log in to start the server.

So we don't tie our Go Server instance to an existing user account we should create another account just for running the server. This allows other users to help with server maintenance without us having to give out our credentials and it also prevents us from accidentally messing up our account if anything goes wrong. Let's create a new administrator account with the username build and log in.

As Go Continuous Delivery is written in Java we should install the latest version of Java if it's not already installed.

Next, we need to download the latest version of Go Server. Unfortunately the Mac OS X version attempts to open a window while starting up which will cause our server to fail to start with the error:

Untrusted apps are not allowed to connect to or launch Window Server before login.

We could attempt to try and make our server a trusted app but trust is not really the issue. In Technical Note TN2083 the section Pre-Login Trust Issues clarifies exactly what is going wrong.

However, this isn't the case. This message is really telling you is that you're trying to connect to the window server from the wrong context. You see this message if you try to connect to the global window server service from outside of the pre-login context before the user has logged in; typically this means that you're trying to use the window server from a daemon.

You should not attempt to fix this by convincing the window server to trust your program; doing so will just cause other problems further down the road. For example, if you do successfully connect to the window server from your daemon, you still have to deal with window server lifecycle issues described previously.

Instead, you should fix this problem by changing your code to run in the correct context. If you need to connect to the window server in a pre-login context, create a pre-login launchd agent. For an example of this, see Sample Code 'PreLoginAgents'.

So to work around this without having to create a complicated daemon we can use the Package version of the server instead. Unzip the server package in to a new directory in our build account's home area called thoughtworks. Open a console window and navigate in to the new directory and we should have something that looks like this:

$ cd ~/thoughtworks
$ pwd
/Users/build/thoughtworks
$ ls
go-server-14.1.0

Next we should create a work area for our server that is separate to the package we just unzipped, this will allow us to later upgrade and keep all our settings. Making a symlink from the package directory that doesn't contain the version number will also make it easier to upgrade later as we won't have to hard code the version number in to any paths.

$ mkdir go-server-work
$ ln -s go-server-14.1.0 go-server-current
$ ls -l
drwxr-xr-x@ 14 build staff 476 Apr 26 14:17 go-server-14.1.0
lrwxr-xr-x 1 build staff 17 Apr 26 13:01 go-server-current -> go-server-14.1.0/
drwxr-xr-x 17 build staff 578 Apr 26 14:34 go-server-work

We should also make sure that the command we use to start the server server.sh is executable.

$ cd go-server-current
$ chmod 755 server.sh
$ ls -l
-rw-r--r--@ 1 build staff 11349 Apr 15 03:36 LICENSE
-rw-r--r--@ 1 build staff 992 Apr 15 03:36 config.properties
-rw-r--r--@ 1 build staff 159 Apr 15 03:36 default.cruise-server
-rw-r--r--@ 1 build staff 134524298 Apr 15 03:36 go.jar
-rw-r--r--@ 1 build staff 3708 Apr 15 03:36 init.cruise-server
-rw-r--r--@ 1 build staff 2696 Apr 15 03:36 server.cmd
-rwxr-xr-x@ 1 build staff 4550 Apr 15 03:36 server.sh
-rw-r--r--@ 1 build staff 866 Apr 15 03:36 start-server.bat
-rw-r--r--@ 1 build staff 879 Apr 15 03:36 stop-server.bat
-rw-r--r--@ 1 build staff 1364 Apr 15 03:36 stop-server.sh

Before setting up the daemon let's just check we can find Java as this will be required:

$ /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java_home
/Library/Java/JavaVirtualMachines/jdk1.7.0_21.jdk/Contents/Home

Now let's make the file that will tell Mac OS X that we want to launch the server as soon as the computer starts. According to the man page for launchd.plist there are five possible locations for agents and daemons:

  • ~/Library/LaunchAgents – Per-user agents provided by the user.
  • /Library/LaunchAgents – Per-user agents provided by the administrator.
  • /Library/LaunchDaemons – System-wide daemons provided by the administrator.
  • /System/Library/LaunchAgents – Per-user agents provided by Mac OS X.
  • /System/Library/LaunchDaemons – System-wide daemons provided by Mac OS X.

We want the server to be launched before any users have logged in so we can ignore all the per-user directories and we are also not Mac OS X so we can avoid /System/Library/LaunchDaemons. This leaves us with /Library/LaunchDaemons let's create a file called thoughtworks.go-server.plist in this directory.

/Library/LaunchDaemons/thoughtworks.go-server.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>UserName</key>
  <string>build</string>
  <key>RunAtLoad</key>
  <true/>
  <key>Label</key>
  <string>thoughtworks.go-server</string>
  <key>EnvironmentVariables</key>
  <dict>
    <key>JAVA_HOME</key>
    <string>$(/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java_home)</string>
    <key>SERVER_WORK_DIR</key>
    <string>/Users/build/thoughtworks/go-server-work/</string>
  </dict>
  <key>WorkingDirectory</key>
  <string>/Users/build/thoughtworks/go-server-work/</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/build/thoughtworks/go-server-current/server.sh</string>
  </array>
  <key>StandardOutPath</key>
  <string>stdout.log</string>
  <key>StandardErrorPath</key>
  <string>stderr.log</string>
</dict>
</plist>

For more thorough information on the contents of .plist files refer to the man page for launchd.plist. Things we should watch out for with this particular file are:

  • UserName should match the name of the administrator account we created.
  • Label must match the name of the file minus the .plist extension.
  • WorkingDirectory will be the location that go-server.log, stdout.log and stderr.log are output.
  • The SERVER_WORK_DIR environment variable will be what server.sh uses as a working directory so should not be the same as the unzipped package directory, this should allow for easier upgrading in the future.

Finally, for Mac OS X to launch this file as a daemon it is not enough for it to be in the relevant directory, it also needs to be owned by root and not writeable by anyone else:

$ cd /Library/LaunchDaemons
$ sudo chown root:wheel thoughtworks.go-server.plist
$ sudo 644 thoughtworks.go-server.plist

Now we're ready to go we can launch the daemon to check that everything is working correctly:

$ sudo launchctl load /Library/LaunchDaemons/thoughtworks.go-server.plist
$ tail -n -1 /var/log/system.log
Apr 26 18:10:31 macosx.home sudo[1694]: build : TTY=ttys000 ; PWD=/Library/LaunchDaemons ; USER=root ; COMMAND=/bin/launchctl load /Library/LaunchDaemons/thoughtworks.go-server.plis

If everything was successful the last line in /var/log/system.log should be our call to launchctl and if we wait for half a minute or so we can then connect to the server instance using browser http://localhost:8153.

If things were not successful then we can check if stdout.log, stderr.log or go-server.log were created in /Users/build/thoughtworks/go-server-work and find out why.

If none of the log files were created then there is probably something wrong with our .plist file permissions but we can check out /var/log/system.log for more information.

The final step is to reboot the Go Server host computer and access Go from another computer without logging in on the host.