Deploying iOS Builds Locally
First published 26 January 2019
If you’re building iPhone and iPad apps, you’ll want a convenient way to deploy builds to your own devices for testing and quick feedback. There are quite a few ways of going about this:
- Just deploy builds manually from Xcode to a connected device. This is the easiest and most obvious approach, but it doesn’t work well if your primary development environment isn’t Xcode (for example, you are building a cross-platform app in Unity or Visual Studio), or if you have graphic artists or designers on your team who don’t usually use Xcode.
- Upload builds to TestFlight, AppCenter (nee HockeyApp), or similar. This is a great way to distribute builds widely for feedback, but can be slow and cumbersome for local development.
- Deploy builds through your own web site, sometimes called “OTA distribution” (for over-the-air). This is what this blog post is about!
With OTA distribution, you can download a recent build of your app from any device over WiFi, by tapping a link on a web page from that device.
The whole pipeline
Before getting into the details, let’s go over the goal and all the different pieces that are needed.
The end goal is to install the Application (a .ipa file built from Xcode) onto the device. This will be served by the Web Server, but iOS can’t install it directly from the browser.
Instead, the device reads an OTA Manifest file (in .plist format) which provides some simple metadata about the app, including the URL of the application itself.
iOS will not install apps over an insecure connection; the manifest must be served over HTTPS. Furthermore, this must be a connection with a trusted certificate. One way to do this is to host a public web server and use a service like Let’s Encrypt to obtain a trusted certificate, but this is not always appropriate for internal developer builds.
Instead, we will sign our own certificate for the web server. As of iOS 12, a self-signed certificate is not sufficient for it to be trusted, even if the certificate is installed on the device. Instead, we must create a Certificate Authority (CA) and have that sign the certificate.
In order to have the iOS device trust our Certificate Authority, we need to install the authority’s Root Certificate on the device. Since iOS 12, this must be done via a Configuration Profile (.mobileprofile), which is a simple file that encapsulates the root certificate.
Once all of this is set up, the user can:
- Download, install and trust the Configuration Profile, which causes the device to accept our Certificate Authority. This only needs to be done once per device.
- Install or update the app by clicking on a link to the OTA Manifest.
The goal here is to obtain:
- A Root Certificate for a Certificate Authority (CA), and
- A Web Certificate for our web server, signed by the same CA. This must have a Subject Name (SN) of the DNS of your web server (e.g., mac.local).
I followed the instructions at How to Create Your Own SSL Certificate Authority for Local HTTPS Development to create the CA and both of these certificates (there’s no need to install the certificate on your computer though).
Set up Web Server
Your web server must be configured to serve HTTPS using the web certificate generated above. For this step, I followed the instructions at Set up a Self Signed Certificate on macOS’s Built in Apache (although we are using a CA-signed certificate, not self-signed).
At this point you should be able to browse your web site over HTTPS, albeit with security warnings indicating that the site is not secure (because your CA is not trusted yet).
The web server must also serve the following MIME types for the manifest and application files:
- application/xml (*.plist)
- application/octet-stream (*.ipa)
For Apache, these are set up in /etc/apache/mime.types; add the plist and ipa extensions to the existing application/xml and application/octet-stream entries, respectively.
In order to install the Root Certificate on an iOS device, it must be packaged in a Configuration Profile (.mobileprofile).
This is easy to create using Apple Configurator 2 from the MacOS App Store — just create a new profile and add the root certificate. There are some more detailed instructions at iPhone Mobile Profile for a new CA root certificate.
This profile should be made available from your web server over HTTP. Users will be prompted to install the profile (with many warnings about its dubious source!). After the profile is installed, it still needs to be added to the trusted root store manually, by opening the Settings / General / About / Trust Certificate Settings and toggling the option for your profile.
At this point you should be able to browse your web server over HTTPS without seeing any security warnings. (Note that this is not just a convenience, it is required for apps to install).
The OTA Manifest is a simple Property List (.plist) file in XML format which describes basic metadata about the app and its download location.
See this Sample Manifest File.
Upload the manifest and app (.ipa) to your website, ensuring they have read permissions available.
Linking to the Manifest
The final step is to create an HTML page with links to each build. The link must be of the form:
<a href="itms-services://?action=download-manifest&url=https://your.domain.com/your-app/manifest.plist">Awesome App</a>
That’s it! Click the link and return to the home screen to see the app install.
In the future I should write about how to automate build delivery to the web server, and how to generate the manifest files and links automatically.
If instead of great success you see an error message on device, check the device console log for a more detailed error message.
A checklist of requirements:
- Manifest .plist file is served over HTTPS from a trusted root certificate (not a self-signed certificate).
- Manifest .plist file is being served with application/xml MIME type.
- Application .ipa file is being served with **application/octet-stream **MIME type.
- Link to the manifest .plist file uses the itms-services form shown above, not HTTP/HTTPS.
- URL of the manifest .plist within the itms-services link is HTTPS.
- Link to application .ipa within manifest .plist is correct (by outsider accounts this does not need to be HTTPS, but may be a requirement in the future).
- Application .ipa file is being served without any cookie authentication.
- Application .ipa file has been signed with a developer, enterprise or ad hoc certificate that is compatible with the target device, and embeds a provisioning profile that explicitly lists the device.
Thanks to draw.io for the stock icons in the pipeline diagram.