This is an explanation of how I built RunSwift. I came up with the idea for RunSwift back in June, just after WWDC. I thought it would be very cool to be able to demo Apple’s new language without the hassle of downloading the Xcode Betas. Not only do you need a Mac but you also need to be a registered developer. There were many people that weren’t able to try the new hotness, which was unfortunate because Swift garnered interest from people in many techonology circles, not just Apple developers. Unfortunately, I was knee deep in building a huge feature in the Fitocracy app so I had to put the idea on the backburner.
Fast forward to the end of August and the new Playback Mode in Fitocracy had shipped (and was incredibly well received!) and I was a few days away from a vacation. I had recently decided to take on some freelance work and knew the best way to get my name out to as many prospects as possible was to build something interesting. So I took out my notes on RunSwift and got to work.
The first thing I had to do was pick my technologies. Obviously I was going to need a Mac and the Swift compiler to do the dirty work, so I setup a remote Mac Mini server with MacStadium and installed Xcode Beta on it. Immediately afterwards, I disabled all remote access to the machine besides SSH.
Next I had to think about how I was going to make the website itself. I decided early on that the web application would be separate from the build server because I didn’t want anyone to have direct access to the Mac Mini for security reasons. It is also rather expensive to host the Mac Mini server so if traffic increased enough that I would need to get a second server it would have exhausted the budget for this little project.
And indeed the night of the launch I did have to get a second server up and running to handle the traffic. Luckily, I chose Heroku as my hosting platform and this was incredibly easy; I just increased the amount of web dynos from one to two and everything ran smoothly.
I figured the web app itself could be pretty lightweight considering it wasn’t going to be doing much other than servering a few templates and relaying compilation requests to the build server.
I chose Sinatra to build the app because I’m most familiar with it and I enjoy working in Ruby. It also has a threaded mode that makes concurrent request processing very easy so I was confident it would be able to handle the compilation requests which would potentially take a few seconds to send and receive. I believe this was the right choice in the end as it led to virtually zero hiccups during a high traffic 24 hour period, successfully handling over 10,000 compiles.
For the build server, I again used Sinatra in threaded mode. It also contains a small POST handler that validates an API key (a different one than is used by the frontend when talking to the web app; this API key is secret and used to ensure that only the webapp can talk to the build server) before processing the request. Once validated, the handler scans the code for a series of blacklisted APIs, such as “import” or “writeToFile”. The request is rejected if it contains any of these APIs. Next, code is prefixed with a few lines of Swift that are saved to a template file and loaded into memory when the build server starts. This template file contains some basic imports like NSLog, NSString, NSArray, NSDictionary, etc. The final Swift code is then saved to a temporary file whose path is passed to a bash script called run-swift. This script compiles and runs the Swift code. Both steps are wrapped by the timeout utility in coreutils to ensure they cannot take more than a few seconds or so. If everything goes well, the script exits with exit status 0. If the compile task fails, it exits with status 1, and if the any of the steps timeout it exits with status 2. This allows the build server to respond with some context around why the request failed.
The execution step of run-swift is delegated off to a second bash script called exec.sh that takes the path to the executable. This file is responsible for running the Swift code in a sandbox by wrapping the call with sandbox-exec using the appropriate permissions. The permissions work like a whitelist. All system access is explicitly disabled, with only a few directories allowed for read access and the execution flag enabled for the Swift executable itself.
When the compile task fails, the compiler output is first cleaned up to remove the path to the temporary files used during the task, and then the result is returned with the appropriate error message.
I launched the website on Hacker News on a Saturday afternoon where it was very well received. The next morning, it was posted to Product Hunt and various other tech sites. Combined, this led to quite a lot of traffic for several days straight and the site never went down. It ran very smoothly and I’m proud of myself for building a system that was resilient enough to be able to handle the traffic.
In the end the app led to many inquiries about consulting work, one of which I signed within the next 48 hours. In total, two of the inquiries led to signed deals. It took 15 hours total to build RunSwift so I’m happy with that return on my investment and I believe it’ll lead to even more work in the future.
That’s pretty much it. RunSwift was a blast to build and I hope you found it and this write up useful!
Thanks for reading.