Skip to content
5 min read

Building iOS app tweaks using GitLab Runner.

Most iOS app tweaks are hosted on GitHub, being the most widely known Git hosting platform. If you are opting for some other provider for your Git repositories, such as GitLab, then if you want to use a pipeline to build the tweaks on your own, you are out of luck unless have a Premium plan so you can get access to the macOS hosted runners. But what if there is another way of building these tweaks using the already existing free hosted runners on GitLab? This is exactly what we are going to explore today.

Theos

Theos is a cross-platform build system for creating iOS, macOS, Linux, and Windows programs. Almost all tweaks that you can sideload use Theos in order to compile the final IPA that you will use.

For example:

Theos in YTLite build

YTLite uses Theos in it's workflow.

Theos in EeveeSpotify build

Same goes for EeveeSpotify

To make iOS builds easier to create, GitHub provides macOS shared runners. Both of the examples above use some sort of these runners (EeveeSpotify uses macos-14, while YTLite uses macos-latest). Both of them also use packages from brew. Fortunately, Theos is cross-compatible and most of the required packages from brew are already available in some form or another on Linux distributions. This got me thinking about creating a Docker image with Theos already installed and ready with the needed dependencies that can be used with GitLab Runner, so that I can easily get my tweaks running from GitLab instead of having to create a GitHub account just to compile them with Actions.

Experimenting with Theos inside Docker

Before getting started, to test this setup, I decided to use YTLite. For this to work, a decrypted YouTube IPA is needed (which due to legal reasons, I cannot provide). After acquiring one, I started building the Docker container using Debian as the base image and following Theos installation guide, only providing the required output when needed. I ended up with this setup:

FROM docker.io/library/debian:latest@sha256:bd73076dc2cd9c88f48b5b358328f24f2a4289811bd73787c031e20db9f97123

RUN DEBIAN_FRONTEND=noninteractive apt update && apt install -y \
bash \
curl \
sudo \
file \
wget \
python3 \
python3-pip \
python3-venv

# defaulting to using user theos when installing and building sdk and tweaks
RUN useradd -m -d /home/theos -s /bin/bash theos && \
echo 'theos:theos' | chpasswd && \
usermod -aG sudo theos && \
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> \
/etc/sudoers

USER theos
# installing theos with all sdks
RUN echo "y\n" | bash -c "$(curl -fsSL https://raw.githubusercontent.com/theos/theos/master/bin/install-theos)"
# installing pipx and cyan 
SHELL ["/bin/bash", "-c"]
RUN pip3 install pipx --break-system-packages && \
~/.local/bin/pipx ensurepath && source ~/.bashrc && \
~/.local/bin/pipx install --force https://github.com/asdfzxcvbn/pyzule-rw/archive/main.zip

CMD bash
  • A separate, non-privileged user is needed in order to install Theos. sudo is also needed. Since this user will need to do privileged tasks, I decided to set NOPASSWD for sudoers in order to avoid unwanted input that could break the image build.
  • cyan is needed for YTLite in order to inject the tweaks.
  • I also went with a full install of Theos, meaning all SDKs for Apple devices are present in the image (in order to provide a unified image for all purposes), which makes the image quite big (~1.4G compressed). Since this image will be exclusively used in GitLab Runners, this is not an issue.

After this, I created a repository with a pipeline that builds and uploads this image to my registry:

docker-build:
  # Use the official docker image.
  image: docker:cli
  stage: build
  services:
    - docker:dind
  variables:
    DOCKER_IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  # All branches are tagged with $DOCKER_IMAGE_NAME (defaults to commit ref slug)
  # Default branch is also tagged with `latest`
  script:
    - docker build --pull -t "$DOCKER_IMAGE_NAME" .
    - docker push "$DOCKER_IMAGE_NAME"
    - |
      if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
        docker tag "$DOCKER_IMAGE_NAME" "$CI_REGISTRY_IMAGE:latest"
        docker push "$CI_REGISTRY_IMAGE:latest"
      fi
  # Run this job in a branch where a Dockerfile exists
  rules:
    - if: $CI_COMMIT_BRANCH
      exists:
        - Dockerfile

You can find the repository at adatcu/theos-dockerized. The built image is also available at:

registry.gitlab.com/adatcu/theos-dockerized:latest

It will be updated regularly in order to make sure all dependencies are met.

Importing YTLite to GitLab

Probably the easiest part of this whole project, as the only thing you have to do is provide the GitHub URL in order to import it. Next, we have to "translate" the GitHub Actions workflow to a GitLab CI pipeline. The first thing I have noticed was that the workflow contained lots of inputs. Fortunately, GitLab Runner supports user inputs so I decided to stick with the following:

spec:
  inputs:
    ipa_url:
      default: https://404.datcuandrei.com/
      type: string
    tweak_version:
      default: 5.2b1
      type: string
    display_name:
      default: YouTube
      type: string
    bundle_id:
      default: com.google.ios.youtube
      type: string
  • Since I wanted all tweaks to be injected, I didn't add the boolean conditionals for tweaks.
  • The default value is null since input will be taken from the user before running the pipeline.
  • The ipa_url variable has a 404 value by default.

Next, since I have already built the Theos docker image with GitLab Runner and pushed it to the registry, that's what I'll use as the base image:

image: registry.gitlab.com/adatcu/theos-dockerized:latest

The next step was adapting the workflow to what GitLab Runner expects.

Since the whole pipeline is big, I've uploaded it to my pastebin. You can find it here.

  • I decided to put the compilation stages into a single one to skip Runner queue times.
  • I have also disabled DontEatMyContent due to compilation errors (code problems).
  • The output IPA will be saved as an artifact.

Now, let's test this pipeline:

Variables required

Before running the pipeline, input is required.

...after ~4 minutes:

Pipeline passed

Success! The final IPA is present in the artifacts. I have also sideloaded it on my Mac to test if it works.

IPA works

And it does without any kind of issues! Which means that our Theos Docker image, hosted on GitLab and used by GitLab Runner, works just fine for building tweaked iOS apps.

Conclusion

This proves that building tweaks for Apple devices doesn't have to be tied just to macOS. With Theos, it's entirely feasible to compile and package iOS tweaks on Linux systems, or virtually any operating system that Docker supports. While this setup was initially tailored for integration with GitLab Runner, it is by no means limited to that use case. I just went this way to preserve the original vision of simplicity and ease of use from the GitHub repository.