Skip to content

Instantly share code, notes, and snippets.

@Selene-Amanita
Last active August 7, 2024 20:31
Show Gist options
  • Save Selene-Amanita/baed7d5854f690f85a1db89ed28a85e2 to your computer and use it in GitHub Desktop.
Save Selene-Amanita/baed7d5854f690f85a1db89ed28a85e2 to your computer and use it in GitHub Desktop.

How to build an app for iPhone on Linux without a Mac

Hi! Since I managed to build/sign/install a Bevy app for iPhone from Linux and I find the documentation online a bit lacking, I thought I would write some words about it. This will be focused on building a Rust app, but the signing/installing steps after "building a .ipa file" step should work for other types of apps too. I'm not an expert in any way, Apple environment is very new to me, some of the stuff I'm not 100% sure how it works, I just want it to be accessible and an incomplete documentation is better than no documentation. And in a way I hope I can help people who are in the same situation as me.

You'll need:

  • An iPhone (obviously) (I have an iPhone 12 with iOS 17.4)
  • An Apple account with an Apple Developer subscription (no way around that, sadly, it's 99€/year, if you're in a team it's possible to have an organisation set up to pay once for everybody)
  • A computer with Linux
  • An app coded in Rust
  • Not a Mac (hopefully, I did use one for the "Set your phone to developer mode" section but it shouldn't be needed)

Preparation

Apple Developer Account

I set up my account on the iPhone I bought specifically to test the app I wanted to test. I created a dummy icloud account, provided a fake name, when asked for a phone number provided a fake one and then clicked "I didn't receive anything, try later" (since I have a notification asking me to do it all the time but it works), later when I had to install some stuff I created an itunes account with the icloud account (it's linked to the Apple Play Store apparently). I think I was able to provide a minimum amount of real personal information in the end. Of course I don't encourage you to do any of that if you actually intend to use your iPhone for anything else than testing an app. I just wanted to say it's possible.

Then you need either:

  • to ask the person in charge of the dev subscription in your organisation to add your icloud account to the organisation
  • to create an account yourself on https://developer.apple.com/ from your computer
  • to create an account yourself on the Apple Developer app from your iPhone.

I went with the first option, but just for information since I explored a bit the second option:

  • the website will try to nudge you heavily into downloading the app, but there is an option at the bottom like "continue on the website".
  • it seems if you started to process with option 2 or 3 but didn't go through (didn't pay) you can't go back to the other one, you still have option 1 available though.

Once you're set-up, you can log in on https://developer.apple.com/, your phone will probably need to be on for 2FA, if everything goes well you should have a page like this (here in French):

apple_dev_1

Click on Certificates, or go here https://developer.apple.com/account/resources/certificates/list, and you'll have a page like this:

apple_dev_2

Later I'll refer to the "Certificates", "Devices" and "Profiles" sections of this page (also accessible from the previous one), and the blue "+" button that you might (or not) see on those pages.

Tools

You'll need:

isign https://github.com/isignpy/isign could also probably be used instead of zsign, but it's not maintained and still using python2 which is a pain to set up. A lot of what I did was from reading their documentation though, especially this page which explains how signing works: https://github.com/isignpy/isign/blob/master/docs/credentials.md , very useful!

xbuild

Rust's xbuild https://github.com/rust-mobile/xbuild is needed to build for iOS and make a .ipa file (the iPhone app archive file, actually a zip file you can peek into, equivalent to .apk).

Follow the instructions, cargo install xbuild, then x doctor to check if you're lacking stuff. Looking at my history I installed the following packages (using apt on Linux Mint 21.3, probably some of them are not needed, maybe I already had some you don't): llvm lldb libllvmspirvlib-15-dev liblldb-15-dev libllvm15 llvm-15-dev kotlin.

x build --help for usage. x devices to check if your phone is correctly connected to your computer.

If you don't have /home/yourusername/.cargo/bin in your PATH environment variable, you might have to add it (see the zsign installation) or to call it like this ~/.cargo/bin/x build.

Notes:

  • If you want to use xbuild to build for android too, here is more resource on that: https://www.nikl.me/blog/2023/github_workflow_to_publish_android_app/ (you can find a blog post about cargo apk on the same blog, but this tool is unmaintained and android only)
  • As we'll see later (see "Build the app" section), xbuild should also be able to sign apps itself, but it doesn't always work, and still requires some preparation, hence this tutorial. If it works for you, you might not need zsign (and maybe not pymobiledevice3 either)

libimobiledevice and pymobiledevice3

https://github.com/libimobiledevice/libimobiledevice / https://github.com/doronz88/pymobiledevice3 libimobiledevice, or the idevice* tools, and pymobiledevice3, both allow you to do a lot of things with an iPhone connected to your computer:

  • get the UDID of the phone (a unique identifier, we'll need it later)
  • get other info about the phone
  • install an app on the phone
  • launch an app on the phone with logs on the computer
  • maybe set your iphone to developer mode? (see section later)
  • many other things

In theory, any of them would be enough, but:

  • If you have an iPhone with iOS 17, libimobiledevice doesn't support launching and debugging an app with it (among other stuff like taking a screenshot), you'll likely need pymobiledevice3 in that case
  • zsign allows to install the app directly after signing it using libimobiledevice

I installed libimobiledevice using sudo apt-get install libimobiledevice6. I tried to build it at some point (I don't remember why, probably the app debugging problem), but it failed (I don't remember why either). You can still find many open issues with the app debugging problem for iOS 17, like this one: libimobiledevice/libimobiledevice#1490 so I'm pretty sure building it yourself wouldn't solve the problem.

For pymobiledevice3 you'll need python3/pip3 on your machine, and following the instructions.

idevice then tab x2 to see all the libimobiledevice commands (idevice_id, ideviceinstaller, ...).

python3 -m pymobiledevice3 --help to see all the commands (and then python3 -m pymobiledevice3 <command> --help to see usage).

If you do have an iOS <=16, you will probably need to download (on the internet?) and mount a Developer disk image (using ideviceimagemounter / pymobiledevice3 mounter probably) to take screenshots or debug an app. You may need your phone to be in developer mode to do that, see bellow.

Rust target

I'm not actually sure if this step is necessary, but just in case you may have to download the rust target corresponding to your phone. You can get your phone's architecture with ideviceinfo | grep Architecture. You can list targets with rustup target list | grep ios, the ones in bold are already installed. Then if needed rustup target add aarch64-apple-ios (for example). More info here: https://rust-lang.github.io/rustup/cross-compilation.html The reason I say I'm not sure it's needed is because my iPhone has an arm64e architecture (compatible arm64, most do probably?), that's what I tell xbuild to use, but it doesn't appear on the list for some reason.

zsign

Compile following the instructions. (optional) I personnally did it in ~/.bin/ a folder I use to compile tools like this, then you can create a link to the zign executable, bring it in ~/.bin/ and add ~/.bin/ to your PATH (export PATH=$PATH:/home/yourusername/.bin, you can put that in your .bashrc file). I think this was straightforward for me, sorry if you have problems. ^^'

Step by step

Build the app

Simply run x build --arch arm64 --platform ios --format ipa (adapt the arch argument if needed). It should run cargo, compile your project, and if everything goes well, build a myapp.ipa file (if your project is named myapp) under /pathtomyprojectroot/target/x/debug/ios/arm64.

Note: there are --pem and --provisioning-profiles option for x build, which theoretically should allow you to sign your app, but I never managed to make it work, probably because the App ID wasn't right, and it doesn't solve the Bevy's asset folder problem. If it works for you, the next steps could be optional (you still need to follow them to have a .pem and .mobileprovision files), if so great for you.

Bevy specific: add the assets folder

There is probably a way to tell xbuild to do it automatically, but you can so it manually. If you have a graphical UI tool to open archives, just double click on the myapp.ipa from the previous step. Inside, go to the myapp.app folder. Drag and drop your project's assets folder inside it. There is probably a way to do it from the command line too.

Important: this step should be done before signing the .ipa, otherwise the signature would be invalid.

Set your phone to developer mode

I'm not 100% sure this is needed, but I think it is, especially if your mobile provisioning profile file (later) is set up for development and not release.

I actually did borrow a MacBook for this step, but I think it might not be needed, at the time I wasn't aware of the existence of pymobiledevice3 or AltServer, and it might not be possible with libimobiledevice (at least on iOS 17).

With a MacBook

You can just borrow a MacBook from someone, ask them to download XCode if they don't have it, launch XCode, no need to provide an account ID, download iOS specific stuff, set-up anything, just freshly opened XCode should work.

Then, with XCode still open, connect your phone to the MacBook (warning: it usually only has USB-C, be ready for that), unlock the phone, (accept the connection to the Mac?,) and go to Settings > Privacy and Security and then scroll all the way down. In the "Security" section you should see "Developer mode" and "Lockdown mode". If you have only Lockdown mode, try uplugging the phone and plugging it again.

Activate the Developer Mode and say yes when asked to restart the phone.

Without a MacBook

I've been told by @SkyLeite it is possible with Altserver on Linux, or AltStore on Windows. I never tested this so you're on your own.

Otherwise I'm not really sure how to do without but maybe python3 -m pymobiledevice3 activation is about that? Not sure.

Create a Certificate signing request/Private key pair on your computer

This is the step that you might find online as "use Keychain Access to generate a Certificate Signing Request" (for example here: https://github.com/isignpy/isign/blob/master/docs/credentials.md#setting-up-credentials or when you're trying to create a certificate on the Apple Developer website in the next step). We will create a personal certificate to say "this is me, this app comes from me, I joined a certificate to it to prove it" (we'll actually join a "super-certificate" including the personal one, see next step).

Simply run openssl req -sha256 -nodes -newkey rsa:2048 -keyout mycert.key -out mycert.csr. You'll be asked a bunch of questions, I'm not sure any of it really matters to be honest, even the challenge password, but don't quote me on that. I don't think you'll need the challenge password after that but best to keep it just in case. You'll get a .key file and a .csr file. The .key file (private key) is a secret, you shouldn't upload it anywhere, it will be used by zsign to sign the app. The .csr file (certificate signing request) proves that something was signed with your private key.

Note: (optional) If needed, you can create a .pem file from the two: cat domaine.fr.csr domaine.fr.key > domaine.fr.pem.

Create a Certificate on the Apple Developer website

What we need actually to provide in the app is a certificate that proved that you signed it with your key, but also that you're approved by Apple to do so, for that we'll generate a "super" certificate containing both information.

  • Go here: https://developer.apple.com/account/resources/certificates/list (Certificates under your dev account)
  • Click the "+" button, located on the right of the Certificate title of the page (or go here https://developer.apple.com/account/resources/certificates/add). Note: you might not have it if you're in an organisation, if it's the case ask the person managing the organisation if they can grant you the right, or do the next sub-steps themselves.
  • Select "iOS App Development" under "Software".
  • You'll be asked to "Upload a Certificate Signing Request", upload the mycert.csr file from the previous step (not the .key file)
  • Download the .cer file that you created, you can give it a name like supercert.cer. You'll be able to download it later too.

certificates

certificates_2

Note: (optional) you shouldn't need a .p12 file at all (it's equivalent to have mycert.key and supercert.cer basically, but the key is protected behind a password, that's what MacOS's Keychain provide). But in case you do, here is how to make one:

  • Get a AppleWWDRCAG3.cer file on the page just after clicking "+" in the steps above, in the "Intermediate Certificates" section, named "Worldwide Developer Relations Certificate Authority", or just click here: https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
  • Get a supercert.pem file with openssl x509 -inform der -in supercert.cer -out supercert.pem, you'll need to provide a password and remember it
  • Get your p12 file with openssl pkcs12 -export -clcerts -inkey mycert.key -in supercert.pem -certfile AppleWWDRCAG3.cer -name "Organisation name" -out supercert.p12
  • you can extract files again from the .p12 file using isign_export_creds.sh from isign (either after installing isign (not zsign), or copying the scrip directly)

Register your device on the Apple Developer Website

devices

Create a Mobile Provisioning Profile on the Apple Developer Website

  • Go here: https://developer.apple.com/account/resources/profiles/list (Devices under your dev account)
  • Click the "+" button (same as two previous steps, https://developer.apple.com/account/resources/profiles/add )
  • Add certificates and devices from the lists. You can pick several of them, but you need to have at least the certificate you created two steps before (supercert.cer) and the device you're using in there.
  • For the App ID, I think you can use anything, but you may need it later, let's say you picked "myapp"
  • Download the file created, you can name it something like team.mobileprovision.

Sign your .ipa file

Assuming:

  • you have zsign in your PATH
  • you have mycert.key and team.mobileprovision files in the root of your project, in which you are
  • you have a .ipa file as explained in the first steps
  • the App ID is "myapp" (I think that might not matter in the end actually)

Run: zsign -f -k mycert.key -m team.mobileprovision -b "myapp" -o myapp_signed.ipa -i target/x/debug/ios/arm64/myapp.ipa

This will provide you with a signed myapp_signed.ipa file.

Note:

  • -f is optional
  • -z 9 to compress the file (?)
  • -k can accept the mycert.pem file from above, or the supercert.p12 file but you'll need to provide the password of the .p12 file with -p

Install your .ipa file on your phone

If you have libimobiledevice installed, you can simply use the -i option of zsign in the command of the previous step, or you can call it manually after: ideviceinstaller -i myapp_signed.ipa. You can probably use python3 -m pymobiledevice3 apps install too.

If you have an error telling you the mobile provisioning profile isn't valid, make sure that you have the right UDID associated with it, the right certificate, and the right App ID (that last one seem to not matter actually but better safe than sorry).

You should be able to launch the app from the app picker of your phone from that point.

Launch the app from the computer to have logs and pass environment variable

If you have iOS 17.4 or more:

  • open 3 terminals
  • in the first terminal: sudo python3 -m pymobiledevice3 remote tunneld
  • in the second terminal:sudo python3 -m pymobiledevice3 lockdown start-tunnel
  • in the third terminal: python3 -m pymobiledevice3 developer dvt launch --stream "myapp", you app should launch on your phone and you should see the logs here

You can also pass --env MY_ENVIRONMENT_VARIABLE_NAME "my environment variable value" to the last command to use a specific environment variable. I don't know of a way to pass arguments to the app, though, but you have at least one way to pass information to it.

If you have between iOS 17.0 and 17.3, then you might need to use rsd instead of lockdown in the second and third terminal, see https://github.com/doronz88/pymobiledevice3?tab=readme-ov-file#working-with-developer-tools-ios--170

If you have iOS 16 or less, then you might be able to do the command in the third terminal if you previously mounted a "Developer disk image", see libimobiledevice's build instruction earlier in this document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment