Setting up a cheap private MacOS runner in Gitlab

Gitlab has been uhming and ahhing about adding MacOS runners for a while but while I’ve been waiting for that I realised I might be better off creating my own dedicated runner.

So why create my own dedicated runner?

  • Not dependent on available shared runners
  • Can install XCode and setup code signing on Mac hardware dedicated to the purpose.
  • Can utilise a USB device for EV code signing for windows.
  • Not restricted to a set amount of minutes for running pipelines.

After deciding to create my own runner I had to decide on what hardware to use. After a lot of research I decided the best bang for buck at this time was to go for a 2012 Mac Mini i5 with 8GB Ram and a 240GB SSD. While not the fastest machine in the world, it should be more than adequate to use as a dedicated pipeline runner. It has support for Catalina and a low 35W TDP make it ideal to leave running. Best of all, it cost just £245.

A similar spec Mac Mini from MacStadium.com would cost at least $59/mo to rent, meaning that it will have paid for itself after just 5 months.

While the initial purpose of getting the machine was to run as a Gitlab runner for MacOS, I ended up using it as a runner to build not just the MacOS application, but also the Windows and Linux ones.

Running Catalina added an extra headache. Catalina dropped support for 32bit applications, which means the wine container I was using couldn’t build the windows installer. What I ended up doing was registering 2 runners on the mac mini by editing ~/.gitlab-runner/config.toml and changing concurrency to 2, then running gitlab-runner register 2 times, the first time I set gitlab-ci tags to mac-linux and the executor to shell and on the second time I set the gitlab-ci tags to windows and the executor to docker and the docker image to electronuserland/builder:wine

gitlab-runner register
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.com/
Please enter the gitlab-ci token for this runner:
ghy8DSDKyN2ixDfSDYzT
Please enter the gitlab-ci description for this runner:
[macusers-iMac.local]: mac-linux
Please enter the gitlab-ci tags for this runner (comma separated):
mac-linux
Registering runner... succeeded                     runner=ehd6KyN2
Please enter the executor: virtualbox, docker-ssh+machine, docker, shell, parallels, ssh, docker+machine, kubernetes, custom, docker-ssh:
shell
Please enter the default Docker image (e.g. ruby:2.6):
electronuserland/builder
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 
gitlab-runner register
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.com/
Please enter the gitlab-ci token for this runner:
ghy8DSDKyN2ixDfSDYzT
Please enter the gitlab-ci description for this runner:
[macusers-iMac.local]: windows
Please enter the gitlab-ci tags for this runner (comma separated):
windows
Registering runner... succeeded                     runner=ehy8KyN2
Please enter the executor: parallels, ssh, virtualbox, docker+machine, docker-ssh+machine, custom, docker, kubernetes, docker-ssh, shell:
docker
Please enter the default Docker image (e.g. ruby:2.6):
electronuserland/builder:wine
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 
gitlab-runner start

I’m using the quasar framework, I install @quasar/cli so I can run the quasar build tool, but this could probably be done directly through electron.builder.

My current .gitlab-ci looks like the following:

stages:
  - build

windows:
  image: electronuserland/builder:wine
  stage: build
  only:
    - tags
  tags:
    - windows
  script:
    - npm install -g @quasar/cli
    - npm install
    - quasar build -m electron -P always -b builder -T win

mac:
  image: electronuserland/builder
  stage: build
  only:
    - tags
  tags:
    - mac-linux
  script:
    - npm install -g @quasar/cli
    - npm install
    - quasar build -m electron -P always -b builder -T mac

linux:
  image: electronuserland/builder
  stage: build
  only:
    - tags
  tags:
    - mac-linux
  script:
    - npm install -g @quasar/cli
    - npm install
    - quasar build -m electron -P always -b builder -T linux

I have it set up to build on tags because I’m using the build version (the tag) and the pipeline in order to name my application and upload to a folder that matches the pipeline id. This means in the backend I can pull in the latest pipelines and select which is the current version, then on the front end the correct version of the app is automatically linked

The builder section of my quasar.conf.js (which would be the same directly on an electron.builder config looks like:

      builder: {
        // https://www.electron.build/configuration/configuration

        appId: 'com.coderior.glasstower',
        afterSign: 'afterSignHook.js',
        publish: {
          'provider': 'spaces',
          'name': 'coderior',
          'region': 'sfo2',
          'path': "glasstower/${env.CI_PIPELINE_ID}"
        },
        win: {
          target: 'nsis',
          artifactName: "${productName}-${version}.${ext}"
        },
        linux: {
          target: 'AppImage',
          artifactName: "${productName}-${version}.${ext}"
        },
        mac: {
          category: 'public.app-category.developer-tools',
          target: 'dmg',
          hardenedRuntime: true,
          entitlements: './src-electron/entitlements.mac.inherit.plist',
          artifactName: "${productName}-${version}.${ext}"
        },
        nsis: {
          license: 'eula.txt'
        },
        appImage: {
          license: 'eula.txt'
        }
      }

In order for it to work properly the package.json version and tag name need to match, but as long as they do it works perfectly.

Leave a Reply

Your email address will not be published. Required fields are marked *