IPA as a Service:

Serving iOS Binaries Directly to User Devices

Harrison Lavin
3 min readMar 23, 2021

A Tidbit: installing the app you’ve been building on an iPhone can be tricky. If you’ve never run into this problem, I can tell you a quick solution. You need a server to host your IPA, a hand-crafted manifest.plist to tell the phone info about the IPA, and an href link that’ll start a call to the manifest via Apple’s itms-services:// protocol. The manifest needs to expose info about the ipa’s location on the server, its bundle identifier, bundle version, and title (most complete ones looks something like this).

A Problem: how do you scale that process so that any arbitrary user can download any arbitrary IPA out of hundreds, if not thousands?

A three-part solution: store the IPA’s info in a database, store the IPA itself in an S3 bucket, and generate the manifest on the fly in your web app.

My team just recently finished deploying that. The finished application consists of three microservices (GRANDSLAM, 3direct, and SHORTSTOP) and one client tool (Pitching Machine).

How it works Now

Getting the IPA up there

All of the binaries we process start from the same place: a mac server farm my team maintains. Once they’re built, they get handed off to Pitching Machine, which begins collecting information on them, and storing them within S3.

From a purely pragmatic standpoint, an .ipa file is just a specialized kind of archive, not too dissimilar to a .zip. This means that, if you rename the .ipa to .zip, you can use standard archive utilities to unzip it. This comes in handy for us, as unzipping it allows us access to the developer-defined Info.plist. We rely on that property list for a lot of information to populate the manifest.plist.

To achieve this with minimal overhead, Pitching Machine copies and transforms the ipa (cp ${filename}.ipa ${filename}.zip ), then unzips it, traversing down the resulting file tree until it finds an info.plist. Once located, the unzip stops and the info collection begins; the download manifest requires bundle-version and bundle-identifier, both of which are exposed in the Info.plist. In addition to the purely necessary manifest information, we store some extra facts in SHORTSTOP, the API sitting in front of our managed MariaDB. We collect the commit ID from the Jenkins git plugin, the app dev version from the IPA, and a rudimentary checksum.

Storing in S3

I wrote a little bit about this before, but suffice to say, we have an API sitting in front of an S3-compliant storage service. Pitching Machine splits the binary into chunks and uploads the chunks piecemeal, only completing the transaction when the API confirms it’s received every piece of the binary.

Bringing it Back Down

The retrieval process is fairly straightforward: we had a manifest service that would generate an XML doc on the fly, identical in structure to a generic manifest.plist. Given a particular artifact’s metadata from the database, we can then populate the manifest with the data required by Apple for proper installation. Then, all it takes is using the java-aws-sdk to getObject out of the S3 bucket, and stream the S3Obj’s content into application/octet-stream within our response entity¹.

¹Our team also uses the same web app for exposing Android builds; the overall process for android is much simpler: since it doesn’t require itms-services, you can just open up the octet-stream directly to the user without the manifest step

--

--