Automating iOS App Development with CI/CD Pipelines with macOS Build Servers

As part of our series on building iOS apps, we will walk through configuring a build server for doing so. This build server can also be used for building macOS apps as well.

This write up is intended to not solve all your CI/CD issues for building apps for iOS, but more of a "bare bones" build server that will help you scale your DevSecOps pipelines for mobile.

To be up front about this, automating builds on macOS has a few pain points. In the pursuit of building a more secure OS, macOS can tend to be on the difficult side for build automation.

For instance, configuring a "headless" build server with FileVault enabled is impossible at this point. So, you cannot VNC into a server sitting in a server rack without doing so locally. Setting up an "auto login" via macOS with FileVault also will not work, because FileVault does not allow that. So, one must take these issues into account.

Without logging in, you cannot (in this instance of this build server) run the GitLab Runners.

So, options can be limited depending on what you are attempting to do. To work around this, you may want to have your macOS boot volume not encrypted and store all your data in an encrypted volume. This will enable the macOS build server to book and auto-login to enable jobs to run.

For GitLab, you need no ingress point to access the build server, only egress to ping your GitLab repo. So, one could drop this box in a private subnet that has some outbound egress and be somewhat comfortable with the security around it.

Automating a lot of these steps hasn't been easy, there are a lot of password and confirmation prompts that require a user to do something.

Options for Build Server

There are a few options. You can buy a server yourself and host it in a rack. You could use Scaleway's M1 instances, AWS now has Mac Mini's, though very expensive, and finally, you could use Mac Stadium. A slew of options. We use local Mac Minis and have been exploring the Scaleway M1 instances—at $90 a month it is hard to beat.

Configuring a Mac OS Build server for iOS Apps

Upon setting up your macOS machine, you will need to install Xcode. We haven't found a good way to automate the installation. You will need to supervise updates and installing the Command Line Tools (required after fresh updates of Xcode).

Download from the App Store app on macOS and you'll be off and running. Thankfully, if you are using Scaleway or have a 1GBPS connection—it is quick.

Installing Brew

For a few elements of the build server, you will need to install brew as a package manager. Run the following command:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Note: Be sure to add the brew path to your PATH variable, either in .bash_profile or .zprofile

Installing AWS CLI 2.0

This is optional and depends if you use any AWS services. We store data with AWS's secure parameter store for configuration and pull from there.

Download and install this package https://awscli.amazonaws.com/AWSCLIV2.pkg

After installation, you will need to configure the AWS CLI credentials, depending on your use case.

Installing the GitLab Runner

First, you will need to install the GitLab runner:

sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64

sudo chmod +x /usr/local/bin/gitlab-runner

cd ~
gitlab-runner install
gitlab-runner start

If you are using M1 Silicon, you will need to install (at this writing) via Homebrew:

brew install gitlab-runner

brew services start gitlab-runner

To get further help for macOS with GitLab go here.

Caveats:

  • GitLab runners only work when someone is logged in
  • You will need to manually update them as they are updated

Configuring the GitLab Runner:

You will need to retrieve your token for the GitLab runner and then execute the following command:

gitlab-runner register \
    --non-interactive \
    --url "https://gitlab.com/" \
    --registration-token "<registration token>" \
    --executor "shell" \
    --description "Monkton iOS" \
    --tag-list "xcode12" \
    --locked="false"

All of these flags can be customized and we strongly suggest reading the documentation on them.

For instance, if you are running a private instance of GitLab, you will need to provide the URL to that instance itself.

XCPretty

During our build process, we leverage XCPretty to fine tune the output for the Xcode Build process.

[sudo] gem install xcpretty

jq

jq is a JSON query tool that allows bash scripts to pull data from JSON files and JSON results from web services.

brew install jq

Xcode Signing Certificates

The signing certificates for your organization will need to be installed and deployed to Keychain to sign your apps. You must export and import these into your machine.

There are options here of putting these in separate keychains (Especially if you have FileVault disabled for the boot partition).

Common Gotchas

We've tried to outline a few common gotchas in the process below.

Build hangs

During the first run of a build, you may need to authorize codesign to sign the build. This will be a modal dialog asking for the password. You will need to enter the password for the current user.

Example dialog text: codesign wants to sign using key "privateKey" in your keychain

xcode-select: error: tool 'xcodebuild' requires Xcode

Run the command:

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

error: exportArchive: No signing certificate "iOS Development" found

If you have installed your certificates, you may run into the issue of the trust chain in Keychain not trusting the certificates. You may need to install the root certificates: https://developer.apple.com/forums/thread/662300