Skip to content

App Framework

An app framework allows you to write an app, simplifying common topics, such as installers, GUIs, etc. Furthermore an app framework allows you to write an app once and build it for several platforms, e.g. Windows, macOS, Linux, Android, iOS, etc., without having to write the same app several times.

The app framework recommended by this guide is Tauri. Tauri supports building your app for Windows, macOS, Linux, Android and iOS. In this guide we will target all of those platforms.

Setup

Install Dependencies

General Dependencies

To use Tauri, you must first install some general dependencies by running the below commands:

sudo dnf install webkit2gtk4.1-devel openssl-devel curl wget file libappindicator-gtk3-devel librsvg2-devel
sudo dnf group install "c-development"

Rust

Tauri requires Rust to be installed. To install Rust, run the below command:

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

When prompted about the installation options, just hit Enter to proceed with the standard installation. Once the installation completes, re-open your terminal so that your PATH environment variable is reloaded. Your PATH should now include the Rust related binaries, e.g. cargo.

Node.js

To use a frontend JavaScript framework with Tauri, we will need to install Node.js. The frontend framework we will be using is Svelte. We have already previously installed Node.js and npm on our development machine in the ESLint section of this guide.

Bun

To use Bun as our package manager we need to install it:

curl -fsSL https://bun.sh/install | bash

The Bun installation should have appended the Bun installation path to our PATH in .bashrc. For this change to take effect, either re-open your terminal or run the below to reload your .bashrc file:

source ~/.bashrc

Android

Android Studio Installation

To target Android in Tauri, we need to have Android Studio installed on our development machine. To install Android Studio, download the latest release of Android Studio for your operating system from the Android Studio website.

Now install some required 32-bit libraries:

sudo dnf install ncurses-libs.i686 bzip2-libs.i686

The official Android Studio installation instructions also list "zlib.i686" as a package to install on Fedora, however this package doesn't seem to exist. The installation instructions seem out of date, so I'm not even sure if any of the 32-bit libraries listed above are required anymore, however there is also no harm in installing them anyway.

Extract the Android Studio archive you downloaded earlier:

cd ~/Downloads
sudo tar -xvf android-studio-2024.2.2.14-linux.tar.gz -C /opt/

Android Studio should now be installed in the /opt/android-studio directory. Now start Android Studio:

cd /opt/android-studio/bin
./studio

Android Studio should now launch. Click "Next" on the dialog:

Android Studio 1

Select "Standard" for the type of setup and click "Next":

Android Studio 2

On the next screen click "Next":

Android Studio 3

Read the license agreement and check "Accept" if you agree. Then click "Next":

Android Studio 4

Some information about KVM is displayed. We will install KVM on our development machine for enhanced Android emulator performance in a later section of this guide. For now click "Finish":

Android Studio 5

Android Studio should now download some required components:

Android Studio 6

Once the download completes, click "Finish":

Android Studio 7

Android Studio should now be ready. Open the "More Actions" accordion and select "SDK Manager":

Android Studio 8

In the SDK Manager go to the "SDK Tools" tab and select the "NDK" and "Android SDK Command-line Tools" items. Then press "Apply". You also need an "Android SDK Platform" (available from the "SDK Platforms" tab), "Android SDK Platform Tools" and "Android SDK Build-Tools", however these should already be installed.

Android Studio 9

Confirm the changes:

Android Studio 10

Read the license agreements, check "Accept" if you accept and then click "Next":

Android Studio 11

The additional software should now download:

Android Studio 12

Once done click "Finish":

Android Studio 13

KVM

For maximum performance of the Android emulator, install KVM on your system by running the below command:

sudo dnf install bridge-utils libvirt virt-install qemu-kvm
sudo dnf install libvirt-devel virt-top libguestfs-tools guestfs-tools

Start and enable the KVM daemon. The KVM daemon should now automatically launch on every boot:

sudo systemctl start libvirtd
sudo systemctl enable libvirtd

KDE Shortcut

To add a KDE shortcut for Android Studio create a new file named "android-studio.desktop" at the location "~/.local/share/applications/". Populate this file with the below content:

android-studio.desktop
[Desktop Entry]
Categories=Development
Comment=Android Studio IDE
Exec=/opt/android-studio/bin/studio
Icon=/opt/android-studio/bin/studio.png
Name=Android Studio
Terminal=false
Type=Application

Now you should be able to launch Android Studio from the KDE start menu:

KDE Android Studio

Environment Variables

For Tauri to be able to find and use your Android Studio installation, you need to set some environment variables. Append the below to your .bashrc file located in your home directory:

.bashrc
export JAVA_HOME=/opt/android-studio/jbr
export ANDROID_HOME="$HOME/Android/Sdk"
export NDK_HOME="$ANDROID_HOME/ndk/$(ls -1 $ANDROID_HOME/ndk)"

For more information about your bash configuration file, see the Configuration section on the CLI page of this guide.

Android Targets

To install the Android targets for Rust, run the below command:

rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android

Create Project

We will now create a new Tauri project. First, we start by creating a new directory named "app" in the root of our Git repository:

mkdir app

Then we navigate into this new directory:

cd app

Inside the app directory we will use the create-tauri-app tool to generate a new Tauri project:

sh <(curl https://create.tauri.app/sh) .

When prompted, choose a project name and identifier. For the rest of the questions, follow the answers provided in the below screenshot:

Tauri Project Generation

We will be using TypeScript, Bun and Svelte in our Tauri app.

Run Project

To run your new Tauri project, we first need to install dependencies by running:

bun install

Now we can launch the Tauri app by running:

bun tauri dev

Tauri should build your project and start your app:

Tauri Run App

Tip

Unfortunately there is currently an issue which may cause a Tauri App to display a blank screen on app startup in Linux.

Fortunately there is a workaround. Append the following to your .bashrc file:

.bashrc
export WEBKIT_DISABLE_DMABUF_RENDERER=1

Then either re-open your terminal or run the below to reload your .bashrc:

source ~/.bashrc

Now when you launch the Tauri app again it should no longer display a blank screen.

To read more about this issue see this Tauri bug ticket and this Restic Browser bug ticket.

To run your project in an Android emulator, start by initializing the project for Android:

bun tauri android init

Now we need to start an emulator. To do so, open Android Studio, open the "More Actions" accordion and select "Virtual Device Manager":

Android Emulator 1

Now start one of your Android emulated devices by clicking on the button with the "Play" icon. Optionally you can also create new Android emulated devices.

Android Emulator 2

Now your Android emulator should have launched:

Android Emulator 3

To run your Tauri app on the Android emulator, run:

bun tauri android dev

Your Tauri app should now be running on the Android emulator:

Android Emulator 4

Linting

To lint the Rust code in our Tauri project we will use Clippy. To lint TypeScript code, we will use ESLint.

Clippy

To use Clippy, we need to install it by running the below command:

rustup component add clippy

Note that Clippy might already be installed. To lint your Tauri project, navigate to the "src-tauri" directory in your Tauri project and run the below command:

cargo clippy

ESLint

To use ESLint in your Tauri project, run the below command in the root directory of your Tauri project to add ESLint and required packages as dependencies to your project:

bun add -D eslint @eslint/js typescript typescript-eslint

In the root directory of your Tauri project, create a file named "eslint.config.mjs" and populate it with the below content:

eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  tseslint.configs.recommended
);

Now we will add an npm script for linting TypeScript code. To do so, open package.json and add the following lines in the scripts section:

package.json
"lint": "eslint src",
"lint:fix": "eslint src --fix",

You can now lint your TypeScript code by running:

bun run lint

Building

To build your Tauri project, run:

bun tauri build

Tip

If your build fails with the following error message:

Error failed to bundle project: failed to run linuxdeploy

Then add the below to your .bashrc file:

export ARCH=x86_64
export NO_STRIP=true

The ARCH should be set to whatever is your development machine's CPU architecture. In my case it is x86_64. After sourcing your .bashrc file the build should now succeed.

After a successful build, you can find the build artifacts in "src-tauri/target/release/bundle/":

Tauri Build Linux

By default, Tauri only builds for your development machine's platform, i.e. in this case for Linux x86_64. To build for other platforms we need to either run the build on the same platform as the desired target platform or use cross-compilation.

On our development machine, we will only build development builds for Linux x86_64 and for Android.

In the CI we will natively build binaries/installers/packages for macOS, Linux x86_64 and Linux ARM64. For the Windows, Android and iOS builds we will use cross-compilation.

To build on our development machine for Android, run the below command:

bun tauri android build

After a successful Android build, you can find the build artifacts in "src-tauri/target/":

Tauri Build Android

Testing

Unit Tests

To run Rust unit tests, run the below command in the "src-tauri" directory:

cargo test

Naturally you don't have any Rust tests yet. We will add them in a later section of this guide.

To run TypeScript tests, we will first need to install the Jest testing framework. Inside your Tauri project's root directory, run the below command:

bun add -D jest ts-jest @types/jest

Now add a "test" script in package.json to run jest:

package.json
"test": "jest --passWithNoTests"

You can now run TypeScript unit tests by running:

bun run test

Naturally you don't have any TypeScript tests yet. We will add them in a later section of this guide.

Integration Tests

To run all Rust integration tests, the same command as for the unit tests is used. Run the below command in the "src-tauri" directory of your Tauri project:

cargo test

To run all TypeScript integration tests, the same command as for the unit tests is used. From the root directory of your Tauri project, run:

bun run test

E2E Tests

As our Tauri app is targets multiple platforms, we need to test on each platform. For more information about E2E testing our Tauri app, see the E2E Tests section on the Testing page in this guide.

Code Coverage

Rust

To see how much of our Rust production code is covered by tests we will use a code coverage tool called Tarpaulin. To install Tarpaulin, run:

cargo install cargo-tarpaulin

You can now get a code coverage report for your Rust code in your project by running the below command in the "src-tauri" directory:

cargo tarpaulin

TypeScript

To see how much of our TypeScript production code is covered by tests we will use Jest. With the correct configuration, Jest not only runs the tests but also generates a code coverage report.

Start by adding some necessary dependencies to your Tauri project. Run the below command in the root directory of your Tauri project:

bun add -D jest-environment-jsdom ts-node

Now generate an initial configuration file for Jest. Run the below command:

npm init jest@latest

On the prompts, give answers as in the below screenshot:

TypeScript Code Coverage

When you now run the below command, you should also get a code coverage report:

bun run test

Since we don't have any Jest tests yet, no code coverage report is generated. We will add Jest tests in a later section of this guide. Later we will also set coverage thresholds, so that "bun run test" will fail if the code coverage is too low. Jest may save coverage data in a directory named "coverage". We need to add this directory to the .gitignore file in the root directory of our Tauri project so that we don't accidentally add any of those coverage files to our Git repository. Append the below line to your .gitignore in the root directory of your Tauri project:

.gitignore
coverage

CI

As we will now add new linting, building and testing stages, it might make sense to remain our existing lint, build and test stages to explicilty mark them as being for web only. Rename the existing pipeline stages from "lint", "build" and "test" to "lint-web", "build-web" and "test-web" respectively. This will require changes in .gitlab-ci.yml, lint.yml, build.yml and test.yml.

Lint Stage

To lint our Tauri app, we will add a new linting stage to our CI pipeline. Modify your .gitlab-ci.yml file to include the below:

.gitlab-ci.yml
stages:
  - lint-app

include:
  - local: "ci/lint-app.yml"

In the "ci" directory of your Git repository, add a file named "lint-app.yml" and add the below content:

lint-app.yml
lint-app:
  before_script:
    - curl -fsSL https://bun.sh/install | bash
    - source ~/.bashrc
    - rustup component add clippy
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  image: ivangabriele/tauri:fedora-40-20
  script:
    - cd app
    - bun install --no-save
    - bun run lint
    - cd src-tauri
    - cargo clippy
  stage: lint-app

Build Stage

Windows

To add a Windows build stage to your CI, modify your .gitlab-ci.yml as per the below:

.gitlab-ci.yml
stages:
  - build-windows

include:
  - local: "ci/build-windows.yml"

In the "ci directory of your Git repository, add a file named "build-windows.yml" and add the below content:

build-windows.yml
build-windows:
  artifacts:
    paths:
      - app/src-tauri/target/x86_64-pc-windows-msvc/release/epic-fantasy-forge.exe
      - app/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe
  before_script:
    - curl -fsSL https://bun.sh/install | bash
    - source ~/.bashrc
    - dnf install -y mingw64-nsis
    - wget https://github.com/tauri-apps/binary-releases/releases/download/nsis-3/nsis-3.zip
    - unzip nsis-3.zip
    - cp nsis-3.08/Stubs/* /usr/share/nsis/Stubs/
    - cp -r nsis-3.08/Plugins/** /usr/share/nsis/Plugins/
    - dnf install -y lld llvm clang
    - rustup target add x86_64-pc-windows-msvc
    - cargo install --locked cargo-xwin
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  image: ivangabriele/tauri:fedora-40-20
  script:
    - cd app
    - bun install --no-save
    - npm run tauri build -- --runner cargo-xwin --target x86_64-pc-windows-msvc
  stage: build-windows

macOS

To add a macOS build stage to your CI, modify your .gitlab-ci.yml as per the below:

.gitlab-ci.yml
stages:
  - build-macos

include:
  - local: "ci/build-macos.yml"

In the "ci directory of your Git repository, add a file named "build-macos.yml" and add the below content:

build-macos.yml
build-macos:
  artifacts:
    paths:
      - app/src-tauri/target/release/bundle/macos/*.app
      - app/src-tauri/target/release/bundle/dmg/*.dmg
  before_script:
    - curl -fsSL https://bun.sh/install | bash
    - export PATH=$PATH:~/.bun/bin
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  script:
    - cd app
    - bun install --no-save
    - bun tauri build
  stage: build-macos
  tags:
    - saas-macos-large-m2pro

Tip

The GitLab hosted macOS runners are currently only available for Premium and Ultimate GitLab users. If your pipeline fails with the error:

No matching runner available

Then it may be because you are using the free version of GitLab. In that case you would either need to upgrade to Premium or Ultimate, or setup a GitLab runner on your own Mac if you have one.

Linux x86_64

To add a Linux x86_64 build stage to your CI, modify your .gitlab-ci.yml as per the below:

.gitlab-ci.yml
stages:
  - build-linux-x86-64

include:
  - local: "ci/build-linux-x86-64.yml"

In the "ci directory of your Git repository, add a file named "build-linux-x86-64.yml" and add the below content:

build-linux-x86-64.yml
build-linux-x86-64:
  artifacts:
    paths:
      - app/src-tauri/target/release/epic-fantasy-forge
      - app/src-tauri/target/release/bundle/appimage/*.AppImage
      - app/src-tauri/target/release/bundle/deb/*.deb
      - app/src-tauri/target/release/bundle/rpm/*.rpm
  before_script:
    - curl -fsSL https://bun.sh/install | bash
    - source ~/.bashrc
    - dnf install -y xdg-utils
    - export ARCH=x86_64
    - export NO_STRIP=true
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  image: ivangabriele/tauri:fedora-40-20
  script:
    - cd app
    - bun install --no-save
    - bun tauri build
  stage: build-linux-x86-64

Linux ARM64

To add a Linux ARM64 build stage to your CI, modify your .gitlab-ci.yml as per the below:

.gitlab-ci.yml
stages:
  - build-linux-arm64

include:
  - local: "ci/build-linux-arm64.yml"

In the "ci directory of your Git repository, add a file named "build-linux-arm64.yml" and add the below content:

build-linux-arm64.yml
build-linux-arm64:
  artifacts:
    paths:
      - app/src-tauri/target/release/epic-fantasy-forge
      - app/src-tauri/target/release/bundle/appimage/*.AppImage
      - app/src-tauri/target/release/bundle/deb/*.deb
      - app/src-tauri/target/release/bundle/rpm/*.rpm
  before_script:
    - dnf install -y xdg-utils
    - dnf install -y webkit2gtk4.1-devel openssl-devel curl wget file libappindicator-gtk3-devel librsvg2-devel
    - dnf group install -y "c-development"
    - dnf install -y nodejs npm
    - dnf install -y unzip
    - curl -fsSL https://bun.sh/install | bash
    - curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
    - source ~/.bashrc
    - export ARCH=aarch64
    - export NO_STRIP=true
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  image: fedora:latest
  script:
    - cd app
    - bun install --no-save
    - bun tauri build
  stage: build-linux-arm64
  tags:
    - saas-linux-small-arm64

Android

To build our Tauri app for Android in the CI, we will create our own Docker image. Start by creating a new directory named "docker" in the root of your Git repository:

mkdir docker

Inside this new directory create a file named "Dockerfile" and populate it with the below content:

Dockerfile
FROM ivangabriele/tauri:fedora-40-20

ENV HOME=/root
ENV ANDROID_HOME=$HOME/cmdline-tools
ENV JAVA_HOME=/usr/lib/jvm/java-21-openjdk
ENV NDK_HOME=$HOME/ndk/29.0.13113456
ENV PATH=$PATH:~/.bun/bin

RUN curl -fsSL https://bun.sh/install | bash
RUN dnf install -y java-21-openjdk-devel.x86_64
RUN cd $HOME && \
  rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android && \
  wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O android-cli-tools.zip && \
  unzip android-cli-tools.zip && \
  cd cmdline-tools && \
  mkdir latest && \
  mv bin latest/ && \
  mv lib latest/ && \
  mv NOTICE.txt latest/ && \
  mv source.properties latest/ && \
  yes | $ANDROID_HOME/latest/bin/sdkmanager "platforms;android-35" "platform-tools" "ndk;29.0.13113456" "build-tools;35.0.0" && \
  cp -r ~/licenses $ANDROID_HOME/.

To build this Docker image locally run:

docker build .

We will want to build this image in the CI so that when we change the Dockerfile a new Docker image is built automatically with the need for manual intervention. To add a Docker stage to your CI, modify your .gitlab-ci.yml as per the below. Place the new "docker-android" stage as the first stage in your CI pipeline.

.gitlab-ci.yml
stages:
  - docker-android

include:
  - local: "ci/docker-android.yml"

In the "ci" directory of your Git repository, add a file named "docker-android.yml" and add the below content:

docker-android.yml
docker-android:
  image: docker:latest
  script:
    - cd docker
    - echo "$CI_JOB_TOKEN" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
    - docker build --pull -t $CI_REGISTRY_IMAGE:tauri-android .
    - docker push $CI_REGISTRY_IMAGE:tauri-android
  services:
    - docker:dind
  stage: docker-android

Tip

You don't need to manually set the CI variables CI_JOB_TOKEN, CI_REGISTRY, and CI_REGISTRY_USER. They are automatically set by GitLab to pull/push docker images from/to your GitLab container registry.

Now that we create a docker image to build Android in the CI, we can add an Android build stage to the CI. To do so, modify your .gitlab-ci.yml as per the below:

.gitlab-ci.yml
stages:
  - build-android

include:
  - local: "ci/build-android.yml"

In the "ci directory of your Git repository, add a file named "build-android.yml" and add the below content:

build-android.yml
build-android:
  artifacts:
    paths:
      - app/src-tauri/gen/android/app/build/outputs/apk/universal/release/*.apk
      - app/src-tauri/gen/android/app/build/outputs/bundle/universalRelease/*.aab
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  image: $CI_REGISTRY/brusecke/epic-fantasy-forge:tauri-android
  script:
    - cd app
    - bun install --no-save
    - export PATH="$HOME/.bun/bin:$PATH"
    - bun run tauri android build --target aarch64
  stage: build-android

iOS

To build our iOS Tauri app in the CI, we must first initialize the project for iOS. We already did this for Android in the Run Project section above. To run this command, we will need a Mac.

If you don't have a Mac, you can temporarily rent one on Scaleway. A Mac mini M4 costs €0.22 per hour. As the minimum rental period is 24 hours, the total cost will be at mimum €5.28. It is not recommended to rent the Mac on a monthly basis as we will rarely need it.

Start by creating an account on Scaleway. Once you have an account, click on your username on the top right and select "SSH Keys":

Scaleway 1

Click on "Add SSH key":

Scaleway 2

On the form, choose a name for your SSH key. In the fingerprint field, copy and paste the contents of your development machine's public key. This can normally be found in a file located at ~/.ssh/id_rsa.pub". For more information about SSH keys, see the SSH Keys section on the Version Control page of this guide.

Scaleway 3

Your public SSH key should now be uploaded to Scaleway. We will now rent a Mac Mini. On the left sidebar, click on "Apple silicon" under the "Bare Metal" category:

Scaleway 1

Click "+ Create Mac mini":

Scaleway 2

Choose the appropriate options on the form to create your Mac Mini instance. Note that sometimes the "M4-S" instance is not available due to low stock. In this case you may need to choose a different type of Mac Mini.

Scaleway 6

Once you are done choosing the appropriate options on the form, read through the terms and conditions and license agreements linked at the bottom of the page. If you agree with the conditions then tick the relevant checkbox and click on "Create Mac mini":

Scaleway 7

Your Mac Mini instance should now be created:

Scaleway 8

Once it is done creating, your instance details should be shown. Click on the "SSH command" field to copy it and paste it into a terminal. Run the command in the terminal to remotely connect to your Mac Mini instance from your development machine. You should not be prompted for a password since you uploaded your public SSH key to Scaleway earlier.

Tip

Note that the Mac you created in Scaleway is not automatically deleted when you are done with it. You have to manually remove it from your Scaleway account when you are done using it, otherwise you will be continuously billed for the running Mac.

Scaleway 9

Developer tools such as Git and Xcode should already be pre-installed on the Mac Mini instances from Scaleway. We will need to configure Git on our instance and add an SSH keypair to it that is authorized to push to our Git repository. The reason for this is that we will initialize our Tauri project for iOS on this Mac instance and then push those changes to our Git repository.

Start by generating an SSH keypair for your Mac. Generate this keypair on your development machine and not the Mac instance. Follow the instructions in the SSH Key Creation section on the Version Control page of this guide. However this time give the generated keypair a custom name, for example "mac_id_rsa" for the private key and "mac_id_rsa.pub" for the public key:

Mac 1

Now upload the newly created keys to your Mac Mini instance:

Mac 2

You can find your Mac Mini's connection details on your Scaleway Console. You should have copied them earlier a few paragraphs above when your Mac Mini instance was created.

SSH into your Mac Mini instance using the connection details shown on the Scaleway Console:

Mac 4

On your Mac Mini, rename the public and private key to their default names:

Mac 3

Now add the Mac Mini public key to your GitLab authorized SSH keys. For more details how to do this see the Adding your Public SSH Key to GitLab section on the Version Control page of this guide.

Mac 5

Now clone your Git repository on the Mac Mini instance. Naturally replace the URL in the example command below with your Git repository's actual URL which you can find from GitLab. For more details about cloning your GitLab repository, see the Cloning a Git Repository section on the Version Control page of this guide.

git clone [email protected]:brusecke/epic-fantasy-forge.git

Navigate into your Tauri project:

cd epic-fantasy-forge/app

As our project depends on Bun, we need to install it:

curl -fsSL https://bun.sh/install | bash

Add Bun to your PATH:

echo 'export PATH="$HOME/.bun/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

We also need to install Rust. When prompted, accept the default options by hitting Enter.

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh

Add Rust to your PATH:

echo '. $HOME/.cargo/env' >> ~/.zshrc
source ~/.zshrc

Additionally we will need to install Node.js. We will install Node.js using the Homebrew package manager. First let's install the Homebrew package manager by running:

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

When prompted for a sudo password, enter the password for your Mac Mini instance. You can find this password on the Scaleway Console:

Scaleway Console

For any other prompts just hit Enter to use the default options. Now add Homebrew to your PATH:

echo >> /Users/m1/.zprofile
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/m1/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

Update Homebrew:

brew update

Now install Node.js:

brew install node

Install the iOS targets:

rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim

Install Cocoapods:

brew install cocoapods

Install the dependencies of your project:

bun install --no-save

Initialize the iOS target in your Tauri project:

bun tauri ios init

We now need to commit the new iOS project to our Git repository. Our Mac does not yet have a .gitconfig. Create one by following the instructions in the Git Configuration section on the Version Control page of this guide. Here is an example .gitconfig for your Mac:

.gitconfig
[user]
    name = Henrik Brüsecke
    email = [email protected]

Now create a new branch in your Git repository and check it out:

git checkout -b ios-project-initialization

Now stage all changes in your project:

git add .

Commit the changes:

git commit -m "Initialize iOS project"

Push the changes:

git push origin ios-project-initialization

Before proceeding, create a merge request for the "ios-project-initialization" branch and merge it to the main branch.

To sign and distribute our iOS app, we need to enroll in the Apple Developer Program. The enrollment costs €99 per year. Start by creating an Apple Account.

Tip

You may need to create a new Apple Account from an Apple device. At least for me creation of an Apple Account failed from my Linux developer machine. Creation of a new Apple acocunt succeeded on the Mac Mini rented from Scaleway. See a little bit below for guidance on how to remotely access your Mac's GUI. You can then use your Mac's GUI to create a new Apple account if you don't have one already.

Once you have an Apple Developer account we need to connect the Scaleway Mac Mini to your Apple Account. Note that despite only using the Mac Mini temporarily, it is mandatory to connect it to our Apple account to proceed with the next steps.

On your Apple Developer account dashboard, click on "Devices":

Apple Developer 1

Click on "Register a Device":

Apple Developer 6

To register the Scaleway Mac Mini, we need its Device ID (UDID). For some parts of this guide we will need access to the GUI of the Mac, i.e. SSH access will not suffice. To find out the UDID, we will use the Mac's GUI.

To remotely access your Mac's GUI, you need a VNC client. In this guide we will use the Remmina VNC client. To install it run:

sudo dnf install remmina

Once it is installed, launch Remmina and enter your VNC connection details. You can find the VNC connection details for your Mac Mini on the Scaleway Console. You will need the Mac's IP address and VNC port:

Scaleway VNC

In Remmina, select "VNC" for the connection type and fill in the IP address and VNC port in the following format: :. Then hit Enter to initiate the VNC connection:

VNC

You may be prompted for a username and password. Enter your Scaleway Mac Mini's username and password. You can find your Mac Mini's credentials from the Scaleway Console:

Scaleway Credentials

VNC 2

You should now be remotely logged into your Mac's GUI:

VNC 3

To find out the UDID of your Mac, click on the Apple icon on the top left of the screen and choose "About This Mac":

UDID 1

Click on "More info...":

UDID 2

Click on "System Report...":

UDID 3

You can now find your Mac's UDID in the "System Information" window in the field "Provisioning UDID". Use this field to populate the "Device ID" field on the "Register a New Device" form. On the form, choose "macOS" as the "Platform" and pick a "Device Name", e.g. "Scaleway Mac Mini". Once you have completed the form, click "Continue":

Apple Developer 7

On the next screen click "Done":

Apple Developer 8

The Mac Mini should now be registered with your Apple Developer account:

Apple Developer 9

Now open your iOS project in Xcode. Start Xcode:

Xcode 1

Click on "Open Existing Project..."

Xcode 2

Navigate to the path where you previously cloned the Tauri project. From the root of your Tauri project, go inside the src-tauri/gen/apple directory and click "Open":

Xcode 3

We will now log into your Apple Developer account from Xcode. Click on "Xcode" in the top left corner and choose "Settings...":

Xcode 4

Select the "Accounts" tab and click the "+" icon to add your Apple Developer account:

Xcode 5

On the next dialog select "Apple ID" and click "Continue" to proceed with the login.

Xcode 6

After you have logged in, your Apple Developer account should be shown:

Xcode 7

Now we need to configure signing of your app. Click on your project, click on your target and select the "Signing & Capabilities" tab. Check the "Automatically manage signing" checkbox. For the "Team", select the team from your Apple Developer account. You can leave the "Bundle Identifier" with the default value.

Xcode 8

You can now verify that your app works by opening a terminal in your VNC session, navigating to your Tauri project's root directory and running:

bun tauri ios dev

When prompted for a simulator to use, pick your preffered choice, e.g. "iPhone 16 Pro Max". Tauri should now build your iOS app and launch the app on the emulator:

iPhone Emulator

You can build your iOS project by running:

bun tauri ios build

Tip

If running the build command from an SSH session rather than a terminal opened in your VNC session, you need to run the below additional command for the build to succeed:

security unlock-keychain

When prompted for a password, enter your Scaleway Mac Mini's password.

You should again have unstaged changes in your Git repository since the project configuration changes we made in Xcode modified some of your project files. Commit these changes to your Git repository by running the below commands in your Tauri project directory:

git checkout -b ios-project-signing
git add .
git commit -m "iOS Project Signing"
git push origin ios-project-signing

Before proceeding, create a merge request for the "ios-project-signing" branch and merge it to the main branch.

To be able to use our Apple Developer account in the CI, we need to use create and App Store Connect API key. To do so, go to App Store Connect and click on "Users and Access":

App Store Connect 1

Click on the "Integrations" tab and select the "App Store Connect API" key on the left sidebar. Click on "Request Access":

App Store Connect 2

Read through the text on the dialog and check the checkbox if you agree. Then click "Submit":

App Store Connect 3

Now we can generate an API key. Choose the "Team Keys" tab and click on "Generate API Key":

App Store Connect 4

Give your new API key a name (e.g. "CI) and set the access level to "Admin". Then press "Generate":

App Store Connect 5

You should now have an App Store Connect API key. Download the private key file by clicking on "Download":

App Store Connect 6

Click "Download":

App Store Connect 7

Once you have downloaded the private key, we must convert it to base64 format and remove the new lines:

base64 AuthKey_XXXXXXXXXX.p8 | tr -d '\n' | cat

"AuthKey_XXXXXXXXXX.p8" above is the name of the file you downloaded. After running the above command a base64 version of your App Store Connect private key should be printed to the console. Copy the value printed to the console.

We will now save this private key to the GitLab CI. Create a new CI variable with the key "APPLE_API_PRIVATE_KEY". Paste the value you copied earlier from the console into the "Value" field of the CI variable. Naturally set the "Visibility" to "Masked and hidden" since the key should be kept secret. Do not tick the "Protect variable" checkbox since we need this variable on non-protected branches also. For more information how to create CI variables see the CI Variables section on the Technical Documentation page of this guide.

App Store Connect 8

Now add two additional CI variables:

  • APPLE_API_ISSUER
    • The value for this key can be found in the Issuer ID field visible on the "App Store Connect API" page (see below)
  • APPLE_API_KEY
    • The value for this key can be found in the "KEY ID" field visiblle on the "App Store Connect API" page (see below)

App Store Connect 10

Finally you should have three Apple CI variables defined in your CI:

App Store Connect 9

Now you can add an iOS build stage to your CI, modify your .gitlab-ci.yml as per the below:

.gitlab-ci.yml
stages:
  - build-ios

include:
  - local: "ci/build-ios.yml"

In the "ci directory of your Git repository, add a file named "build-ios.yml" and add the below content:

build-ios.yml
build-ios:
  artifacts:
    paths:
      - app/src-tauri/gen/apple/build/arm64/*.ipa
  before_script:
    - curl -fsSL https://bun.sh/install | bash
    - export PATH="$HOME/.bun/bin:$PATH"
    - curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y
    - . $HOME/.cargo/env
    - NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
    - echo >> /Users/gitlab/.zprofile
    - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> /Users/gitlab/.zprofile
    - eval "$(/opt/homebrew/bin/brew shellenv)"
    - brew update
    - brew install node
    - rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim
    - brew install cocoapods
    - echo $APPLE_API_PRIVATE_KEY | base64 -d > /Users/gitlab/apple_api_private_key
    - export APPLE_API_KEY_PATH=/Users/gitlab/apple_api_private_key
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  script:
    - cd app
    - bun install --no-save
    - bun tauri ios build
  stage: build-ios
  tags:
    - saas-macos-large-m2pro

Test Stage

To test our Tauri app, we will add a new testing stage to our CI pipeline. Modify your .gitlab-ci.yml file to include the below:

.gitlab-ci.yml
stages:
  - test-app

include:
  - local: "ci/test-app.yml"

In the "ci" directory of your Git repository, add a file named "test-app.yml" and add the below content:

test-app.yml
test-app:
  before_script:
    - curl -fsSL https://bun.sh/install | bash
    - source ~/.bashrc
    - cargo install cargo-tarpaulin
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - app/src-tauri/target
      - app/node_modules
  image: ivangabriele/tauri:fedora-40-20
  script:
    - cd app
    - bun install --no-save
    - bun run test
    - cd src-tauri
    - cargo tarpaulin
  stage: test-app

Visual Studio Integration

In our Tauri project we use ESLint to lint our TypeScript code, just like we already do in our Phoenix project. Installation of the ESLint Visual Studio extension is covered in the ESLint section on the Web Framework page of this guide.

Svelte

To add support for Svelte to Visual Studio Code, run the below command:

code --install-extension svelte.svelte-vscode

Rust

To add support for Rust to Visual Studio Code, run the below command:

code --install-extension rust-lang.rust-analyzer

Tauri

To add support for Tauri to Visual Studio Code, run the below command:

code --install-extension tauri-apps.tauri-vscode

Cleanup

Your auto-generated Tauri project contains a lot of demo code to display the default screen of the Tauri project. Let's remove all of this demo code so you have an empty screen instead of the demo screen displayed when you run the Tauri project.

Start by completely empyting the "+page.svelte" file located at "src/routes":

+page.svelte

Next replace the demo title in "src/app.html" with your actual app's title:

app.html
<title>Epic Fantasy Forge</title>

Delete the below lines from "src-tauri/src/lib.rs":

lib.rs
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hello, {}! You've been greeted from Rust!", name)
}
lib.rs
.invoke_handler(tauri::generate_handler![greet])

Modify "src-tauri/tauri.conf.json" to have your app's name as the window title:

tauri.conf.json
"title": "Epic Fantasy Forge",

Your cleaned app should now look like this:

Cleaned App

Tailwind CSS

We will use Tailwind CSS in our Svelte frontend. To add Tailwind CSS, navigate to your Tauri project's root directory and run the below command:

bun add -D tailwindcss @tailwindcss/vite

Now add Tailwind CSS to your Vite configuration file:

vite.config.js
import tailwindcss from '@tailwindcss/vite';

export default defineConfig(async () => ({
  plugins: [sveltekit(), tailwindcss()],

In your Tauri project's "src" directory, create a file named "app.css" and populate it with the below content:

app.cs
@import "tailwindcss";

Now take Tailwind CSS into use in your "+page.svelte" file which can be found under "src/routes":

+page.svelte
<script>
  import "../app.css";
</script>

<style lang="postcss">
  @reference "tailwindcss";
</style>