Build Standalone Expo .apk and .ipa with Turtle CLI
January 08, 2020 - 24 min
This article was updated on January 08, 2020. The previous version from March 12, 2019 is still available.
A great way to start writing React Native applications is to use Expo. You don’t even need to setup a development environment to get started thanks to Snack, an online React Native application editor. Your application is public by default. Sharing has never been that easy.
Brown turtle swimming underwater, Photo by Wexor Tmg on Unsplash
However, you would like to share an application exclusively with some people, not through an app store or any public channel. One possibility is to share the built application file, e.g. an .apk
file for Android and an .ipa
file for iOS.
Expo is so great a service (oh, and did I mention free?) that they offer you to publish your app on their servers and if you wanted to build these .apk
and .ipa
files, you could do so on their servers.
The flow looks like:
$ expo publish
$ expo build:<PLATFORM>
and you obtain a nice URL to the artifact created for you when the build finishes.
But, again, this is public. On a server you don’t control. (Although, if you made the same mistake as I did, a ticket to their support can delete any files you had wished to keep private. 🥇)
Luckily for you, Expo provide the tools they are using on their servers for free (did I mention they’re great!?). The build process relies on a CLI tool called turtle-cli
. It is open-source and available on GitHub.
Let us go through an example of how to create your application file without resorting to Expo servers. That means you can keep an application private and also build your application while being offline.
The following instructions assume you have bash
and Node.js (version 8 or newer) available. In my experience, using the current LTS version of Node.js (version 10.15.3) works best.
Caveat: I could not complete the iOS instructions to the end since I do not have an Apple Developer paid account.
We will go through the following steps:
A condensed version of these steps without much explanation or context is available on the accompanying GitHub repository.
1. Turtle CLI
To install Turtle CLI, run
$ npm install -g turtle-cli
This will make command turtle
globally available on your system. To verify it was correctly installed, let’s print the version:
$ turtle -V
0.13.6
You can also print the CLI manual by using the flag --help
:
$ turtle --help
Usage: turtle [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
setup:ios|si [options] Setup environment for building Ios standalone apps.
setup:android|sa [options] Setup environment for building Android standalone apps.
build:ios|bi [options] [project-dir] Build a standalone IPA for your project, signed and ready for submission to the Apple App Store.
build:android|ba [options] [project-dir] Build a standalone APK or App Bundle for your project, either signed and ready for submission to the Google Play Store or in debug mode.
2. Create a dummy application
Let’s create a dummy application using Expo CLI. In your terminal, run
$ npx expo init ExampleApplication
(This assumes you have at least version 5.2.0 for npm
; you could otherwise install Expo CLI globally with npm install -g expo-cli
).
It will ask you a few questions:
# Expo version 3.11.3
? Choose a template: (Use arrow keys)
----- Managed workflow -----
❯ blank a minimal app as clean as an empty canvas
blank (TypeScript) same as blank but with TypeScript configuration
tabs several example screens and tabs using react-navigation
----- Bare workflow -----
minimal bare and minimal, just the essentials to get you started
minimal (TypeScript) same as minimal but with TypeScript configuration
Blank template is enough for us.
? Choose a template: expo-template-blank
? Please enter a few initial configuration values.
Read more: https://docs.expo.io/versions/latest/workflow/configuration/ › 50% completed
{
"expo": {
"name": "<The name of your app visible on the home screen>",
"slug": "ExampleApplication"
}
}
Provide an app name: ExampleApplication will do for example.
? Choose a template: expo-template-blank
✔ Please enter a few initial configuration values.
Read more: https://docs.expo.io/versions/latest/workflow/configuration/ · 100% completed
? Yarn v1.19.2 found. Use Yarn to install dependencies? (Y/n)
You can choose yarn
to install dependencies if you have it installed like me, but npm
should work just as well.
Once it’s done setting up your project, you should see the following output in the terminal:
Your project is ready at ./ExampleApplication
To get started, you can type:
cd ExampleApplication
yarn start
Let’s verify the generated boilerplate works:
To finish setting up the dummy application, you need to configure the app.json
file because some configuration keys must be specified to build a standalone app.
If you would like to know more about these standalone app specific configurations, check Expo’s documentation for iOS and for Android.
For this tutorial, it is only necessary to specify the keys "bundleIdentifier"
under "ios"
, and "package"
under "android"
in your app.json
file:
...
"ios": {
...
"bundleIdentifier": "com.example.exampleApplication",
...
},
"android": {
...
"package": "com.example.example_application",
...
}
...
3. Publish Expo app on local server
Expo has this notion of app publishing: it allows you to share a link or QR code to Expo’s website and anyone can run your app through the Expo app on their devices.
Having your app published is also necessary for over-the-air (OTA) updates, allowing seamless updates to your application, and to publish your application (i.e. building your application files on Expo servers).
You would typically run
$ expo login -u $EXPO_USERNAME -p $EXPO_PASSWORD
$ expo publish
and your app would then be available at https://expo.io/<EXPOUSERNAME>/<APPNAME>.
To have your Expo app “published” on your local server (and avoid public publishing on Expo servers), you will use a different command:
$ expo export --dev --public-url <your-url-here>
The --dev
flag is necessary because you will use a non-https server to publish your Expo app.
Run a local server
There are several ways you could run a local server where you will publish your Expo app.
-
with Python
Make sure you are using Python 3 since Python 2 is no longer maintained since January 1, 2020. Go to the folder and run
$ python --version # If Python version returned above is 2.X, # try to use python3 instead $ python -m http.server # OR $ python3 -m http.server # Otherwise, upgrade your Python installation
You should be able to go to http://127.0.0.1:8000 to see the files available in the current folder.
-
This Chrome extension allows you to run a web server through a user interface.
In the following, we will assume that your local web server is running on http://127.0.0.1:8000
.
Export the app
Your server does not need to be running for this step, but you do need to know which local URL you are going to use in advance.
As introduced above, you can now run:
$ expo export --dev --public-url http://127.0.0.1:8000
...
Export was successful. Your exported files can be found in dist
This command will create a dist
directory containing the iOS and Android JavaScript bundles and the different assets you are using.
dist
├── android-index.json
├── assets
│ ├── 140c53a7643ea949007aa9a282153849
│ ├── 3a2ba31570920eeb9b1d217cabe58315
│ ├── 43ec0dcbe5a156bf9e650bb8c15e7af6
│ ├── 5cdf883b18a5651a29a4d1ef276d2457
│ ├── 6beba7e6834963f7f171d3bdd075c915
│ ├── 73b8cff012825060b308d2162f31dbb2
│ ├── 744ce60078c17d86006dd0edabcd59a7
│ ├── a37b0c01c0baf1888ca812cc0508f6e2
│ ├── b06871f281fee6b241d60582ae9369b9
│ ├── b2e0fc821c6886fb3940f85a3320003e
│ ├── d15c1216957060fac577af6151fb8cfe
│ ├── d2285965fe34b05465047401b8595dd0
│ ├── e20945d7c929279ef7a6f1db184a4470
│ └── fa6577fecc0a7838f15a254577639984
├── bundles
│ ├── android-0d2eb108fcf8b4015c36718ff6556ff4.js
│ └── ios-93c994cb24cc6ed3dd007d5d45b11908.js
└── ios-index.json
You can now serve the dist
directory on your web server, e.g.
$ cd dist
$ python -m http.server
# OR
$ python3 -m http.server
Note: if for some reason you need to re-export your application (because you modified your app.json
file since the last export, for instance), you must first remove the dist
directory:
# assuming you are at the root of the project
$ rm -rf dist
$ expo export --dev --public-url http://127.0.0.1:8000
4. Create APK file — Android
First, verify that your local server is running, e.g.
$ curl http://127.0.0.1:8000/android-index.json
{"name":"ExampleApplication","slug":"ExampleApplication","privacy":"public","sdkVersion":"36.0.0","platforms":["ios","android","web"],"version":"1.0.0","orientation":"portrait","icon":"./assets/icon.png","splash":{"image":"./assets/splash.png","resizeMode":"contain","backgroundColor":"#ffffff","imageUrl":"http://127.0.0.1:8000/assets/43ec0dcbe5a156bf9e650bb8c15e7af6"},"updates":{"fallbackToCacheTimeout":0},"ios":{"supportsTablet":true,"bundleIdentifier":"com.example.exampleApplication"},"android":{"package":"com.example.example_application"},"locales":{},"iconUrl":"http://127.0.0.1:8000/assets/f82b34f900882c5120a1bfbf6df22a27","bundledAssets":["asset_3a2ba31570920eeb9b1d217cabe58315.ttf","asset_8b12b3e16d591abc926165fa8f760e3b.json","asset_744ce60078c17d86006dd0edabcd59a7.ttf","asset_461d9bba8b6a3c91675039df12cfe6ca.json","asset_140c53a7643ea949007aa9a282153849.ttf","asset_94c4ffdcbffeb0570c635d7f8edd8a25.json","asset_6beba7e6834963f7f171d3bdd075c915.ttf","asset_648f2d510967a87880abfed9476aeb28.json","asset_b06871f281fee6b241d60582ae9369b9.ttf","asset_f1f91feb805137c9283fb766620ec5eb.json","asset_09dd345dbd4ec5a0874841d5749ac153.json","asset_0886a6b127c6057cee83f9c65c7ffd62.json","asset_2e562d4ebf15395f00bc738738f79291.ttf","asset_872545dde71de3842234bf6afe80c4cb.ttf","asset_c6aef942e3668158ec29d4adcb2e768f.ttf","asset_e20945d7c929279ef7a6f1db184a4470.ttf","asset_60668d999bbaf663420340f7bdd580d7.json","asset_b2e0fc821c6886fb3940f85a3320003e.ttf","asset_3e6805fbc794680014716b8c752f20b8.json","asset_5a293a273bee8d740a045d9922b9a9ae.ttf","asset_b582e1c8a605c3b9a1c26e09789a78d4.json","asset_a37b0c01c0baf1888ca812cc0508f6e2.ttf","asset_7e078700f0c35367a56c5bbb2047dda7.json","asset_8e7f807ef943bff1f6d3c2c6e0f3769e.ttf","asset_fdc01171a7a7ea76b187afcd162dee7d.json","asset_d2285965fe34b05465047401b8595dd0.ttf","asset_647543ebfccf6e5495434383598453d1.json","asset_5cdf883b18a5651a29a4d1ef276d2457.ttf","asset_74d124a3caeac2bea111f3ca2f2dd34a.json"],"assetUrlOverride":"./assets","publishedTime":"2020-01-10T08:40:46.255Z","commitTime":"2020-01-10T08:40:46.255Z","revisionId":"XwJm9wdyZw","developer":{"tool":"exp"},"id":"@anonymous/ExampleApplication","bundleUrl":"http://127.0.0.1:8000/bundles/android-180fb088cff97225a61024176ed1af3a.js","platform":"android","dependencies":["expo","react","react-dom","react-native","react-native-web"]}⏎
4.1. Prerequisites
For Turtle CLI to work correctly to create an .apk
file for Android, you need to make sure the following dependency is installed: Java Development Kit (version 8).
If you know which Expo SDK version you are going to use, you can run
$ turtle setup:android --sdk-version <SDK-VERSION>
This will take some time:
$ turtle setup:android --sdk-version 36.0.0
Jan 8 18:59:05 turtle[88451] INFO: shell app for SDK 36.0.0 doesn't exist, downloading...
platform: "android"
buildPhase: "setting up environment"
downloading [========== ] 51% 15.3s
...
Jan 8 18:59:33 turtle[88451] INFO: shell app has been downloaded
platform: "android"
buildPhase: "setting up environment"
Jan 8 18:59:33 turtle[88451] INFO: extracting shell app (this may take a while)...
platform: "android"
buildPhase: "setting up environment"
...
Jan 8 19:01:32 turtle[88451] INFO: ✅ Verified that all workspace dependencies are symlinked.
platform: "android"
buildPhase: "setting up environment"
source: "stdout"
Jan 8 19:01:32 turtle[88451] INFO: Done in 94.07s.
platform: "android"
buildPhase: "setting up environment"
source: "stdout"
Jan 8 19:01:32 turtle[88451] INFO: dependencies installed!
platform: "android"
buildPhase: "setting up environment"
Jan 8 19:01:32 turtle[88451] INFO: it's all set!
platform: "android"
buildPhase: "setting up environment"
In the meantime, you can move on to the next section.
4.2. Create Keystore
Just like any other Android app, this app must be signed with some security certificates. In Android-land, as far as I understand, it’s called a keystore. The instructions to create such a keystore file are available on the Android Developers documentation.
Basically, just create a dummy Android project with Android Studio, and then go to Build > Generate Signed Bundle / APK...
. Choose APK
and press Next
. The UI will ask you which app you want to sign, and your dummy app should be pre-selected. On the same screen, press on the button Create new...
; just fill the form and you’re good to go.
Alternatively, if you have keytool
CLI utility available on your system (you can check that by trying keytool --help
in your terminal), you can generate a keystore file with the following command
$ keytool -genkeypair -v -keystore keystore.jks -alias keyalias -keyalg RSA -keysize 2048 -validity 9125
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]:
What is the name of your organizational unit?
[Unknown]:
What is the name of your organization?
[Unknown]:
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]:
Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
[no]: yes
Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 9,125 days
for: CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown
Enter key password for <keyalias>
(RETURN if same as keystore password):
Re-enter new password:
[Storing keystore.jks]
Warning:
The JKS keystore uses a proprietary format. It is recommended to migrate to PKCS12 which is an industry standard format using "keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.jks -deststoretype pkcs12".
As a TL;DR, you can just download a dummy certificate on the accompanying repository under should-be-private/keystore.jks
, for testing purposes only. The keystore file is hugely important if you ever plan to submit your app to Google Play store: it’s the only way to assert you’re the rightful owner to provide new updates to your app.
File: keystore.jks
Keystore Password:keystorepassword
Keystore Key Alias:keyalias
Keystore Key Password:keypassword
4.3. Build APK
Make sure that you are serving the dist
directory on http://127.0.0.1:8000 as explained above in Run a local server. Assuming you are using bash
as your shell, from the root of your project, run:
EXPO_ANDROID_KEYSTORE_PASSWORD="keystorepassword" \
EXPO_ANDROID_KEY_PASSWORD="keypassword" \
turtle build:android \
--type apk \
--keystore-path ./should-be-private/keystore.jks \
--keystore-alias "keyalias" \
--allow-non-https-public-url \
--public-url http://127.0.0.1:8000/android-index.json
Note that you need the flag --public-url
to point to http://127.0.0.1:8000/android-index.json
. In addition, it is also necessary to use the --type apk
flag to produce an APK file since version 0.8.0 of turtle-cli
.
The build may take some time: it seemed like the build froze on several occasions, but it eventually printed logs again after a few minutes of silence, so patience is required:
...
Jan 9 21:57:49 turtle[76552] INFO: Could not find google-services.json while looking in [src/nullnull/release, src/release/nullnull, src/nullnull, src/release, src/nullnullRelease]
platform: "android"
buildPhase: "running gradle"
source: "stdout"
Jan 9 21:57:49 turtle[76552] INFO: registerResGeneratingTask is deprecated, use registerGeneratedResFolders(FileCollection)
platform: "android"
buildPhase: "running gradle"
source: "stdout"
#
# ~25 minutes
#
Jan 9 22:21:21 turtle[76552] INFO: > Task :app:preBuild UP-TO-DATE
platform: "android"
buildPhase: "running gradle"
source: "stdout"
Jan 9 22:21:21 turtle[76552] INFO: > Task :app:extractProguardFiles
platform: "android"
buildPhase: "running gradle"
source: "stdout"
...
Jan 9 22:25:49 turtle[76552] INFO: > Task :expo-ads-facebook:compileReleaseJavaWithJavac
platform: "android"
buildPhase: "running gradle"
source: "stdout"
Jan 9 22:26:19 turtle[76552] INFO: > Task :expo-ads-facebook:extractReleaseAnnotations
platform: "android"
buildPhase: "running gradle"
source: "stdout"
#
# 10 minutes
#
Jan 9 22:36:25 turtle[76552] INFO: > Task :expo-ads-facebook:mergeReleaseGeneratedProguardFiles
platform: "android"
buildPhase: "running gradle"
source: "stdout"
Jan 9 22:36:25 turtle[76552] INFO: > Task :expo-ads-admob:mergeReleaseGeneratedProguardFiles
platform: "android"
buildPhase: "running gradle"
source: "stdout"
...
Once the build finishes, you should see this output in your terminal:
Jan 9 22:38:42 turtle[76552] INFO: copying build to fake upload directory
platform: "android"
buildPhase: "copying build artifact"
Jan 9 22:38:42 turtle[76552] INFO: copied build to ~/expo-apps/@anonymous\ExampleApplication-9be976cea1fb4651a6fa04d8432873eb-signed.apk
This message gives you the location of your built .apk
file. You can now move on to the Distribute and install section below if you are not interested in creating an .ipa
file for iOS.
5. Create IPA file — iOS
First, verify that your local server is running, e.g.
$ curl http://127.0.0.1:8000/ios-index.json
{"name":"ExampleApplication","slug":"ExampleApplication","privacy":"public","sdkVersion":"36.0.0","platforms":["ios","android","web"],"version":"1.0.0","orientation":"portrait","icon":"./assets/icon.png","splash":{"image":"./assets/splash.png","resizeMode":"contain","backgroundColor":"#ffffff","imageUrl":"http://127.0.0.1:8000/assets/43ec0dcbe5a156bf9e650bb8c15e7af6"},"updates":{"fallbackToCacheTimeout":0},"ios":{"supportsTablet":true,"bundleIdentifier":"com.example.exampleApplication"},"android":{"package":"com.example.example_application"},"locales":{},"iconUrl":"http://127.0.0.1:8000/assets/f82b34f900882c5120a1bfbf6df22a27","bundledAssets":["asset_3a2ba31570920eeb9b1d217cabe58315.ttf","asset_8b12b3e16d591abc926165fa8f760e3b.json","asset_744ce60078c17d86006dd0edabcd59a7.ttf","asset_461d9bba8b6a3c91675039df12cfe6ca.json","asset_140c53a7643ea949007aa9a282153849.ttf","asset_94c4ffdcbffeb0570c635d7f8edd8a25.json","asset_6beba7e6834963f7f171d3bdd075c915.ttf","asset_648f2d510967a87880abfed9476aeb28.json","asset_b06871f281fee6b241d60582ae9369b9.ttf","asset_f1f91feb805137c9283fb766620ec5eb.json","asset_09dd345dbd4ec5a0874841d5749ac153.json","asset_0886a6b127c6057cee83f9c65c7ffd62.json","asset_2e562d4ebf15395f00bc738738f79291.ttf","asset_872545dde71de3842234bf6afe80c4cb.ttf","asset_c6aef942e3668158ec29d4adcb2e768f.ttf","asset_e20945d7c929279ef7a6f1db184a4470.ttf","asset_60668d999bbaf663420340f7bdd580d7.json","asset_b2e0fc821c6886fb3940f85a3320003e.ttf","asset_3e6805fbc794680014716b8c752f20b8.json","asset_5a293a273bee8d740a045d9922b9a9ae.ttf","asset_b582e1c8a605c3b9a1c26e09789a78d4.json","asset_a37b0c01c0baf1888ca812cc0508f6e2.ttf","asset_7e078700f0c35367a56c5bbb2047dda7.json","asset_8e7f807ef943bff1f6d3c2c6e0f3769e.ttf","asset_fdc01171a7a7ea76b187afcd162dee7d.json","asset_d2285965fe34b05465047401b8595dd0.ttf","asset_647543ebfccf6e5495434383598453d1.json","asset_5cdf883b18a5651a29a4d1ef276d2457.ttf","asset_74d124a3caeac2bea111f3ca2f2dd34a.json"],"assetUrlOverride":"./assets","publishedTime":"2020-01-10T08:40:46.255Z","commitTime":"2020-01-10T08:40:46.255Z","revisionId":"XwJm9wdyZw","developer":{"tool":"exp"},"id":"@anonymous/ExampleApplication","bundleUrl":"http://127.0.0.1:8000/bundles/ios-404585eb9ae529b61ed72e5df8a757ad.js","platform":"ios"}⏎
5.1. Prerequisites
For Turtle CLI to work correctly to create an .ipa
file for iOS, you need to make sure your system is macOS and that the following dependencies are installed:
- Xcode (version 9.4.1 or newer)
- fastlane
Note: make sure that you have run Xcode at least once and that you have agreed to the license agreements.
For more details, see Expo’s documentation.
If you know which Expo SDK version you are going to use, you can run
$ turtle setup:ios --sdk-version <SDK-VERSION>
If it errors out, saying that you don’t have the right version of
fastlane
, try re-installingfastlane
(guide). The version used in this article isfastlane 2.140.0
.
This will take some time:
$ turtle setup:ios --sdk-version 36.0.0
Jan 9 22:08:07 turtle[97409] INFO: shell app for SDK 36.0.0 doesn't exist, downloading...
platform: "ios"
buildPhase: "setting up environment"
downloading [ ] 1% 8514.8s
...
...
Jan 9 23:57:37 turtle[97409] INFO: shell app extracted
platform: "ios"
buildPhase: "setting up environment"
Jan 9 23:57:37 turtle[97409] INFO: it's all set!
platform: "ios"
buildPhase: "setting up environment"
In the meantime, you can move on to the next section.
5.2. Create Signing keys
This assumes you have an Apple Developer Account, which is a paid account.
To create your distribution certificate file, or .p12
file, you can follow this guide.
You will also need your Apple Team ID and a Provisioning Profile that you can generate through Xcode as well. This other guide covers in greater lengths what all these cerficates are for and how to obtain them.
Note that the provisioning profile expires in 6 days in my case, since I do not have an Apple Developer paid account.
Sadly, it is not possible to share example files.
5.3. Build IPA
Make sure that you are serving the dist
directory on http://127.0.0.1:8000 as explained Run a local server. Assuming you are using bash
as your shell, from the root of your project, run:
EXPO_IOS_DIST_P12_PASSWORD="<PASSWORD HERE>" \
turtle build:ios \
--team-id YOUR_TEAM_ID \
--dist-p12-path /path/to/your/dist/cert.p12 \
--provisioning-profile-path /path/to/your/provisioning/profile.mobileprovision \
--allow-non-https-public-url \
--public-url http://127.0.0.1:8000/ios-index.json
Note that you need the flag --public-url
to point to http://127.0.0.1:8000/ios-index.json
.
Once the build finishes successfully, you should get the path to your .ipa
file.
6. Distribute and install
6.1. on Android
Take your .apk
and share it through some Google Drive link or other medium of your choice.
If you download this .apk
file through an Android device, it should offer you to install the application on the device.
Trick: if the URL is really not friendly to type, and for whatever reason you have to type it manually, use a QR Code generator (e.g. http://www.barcode-generator.org/) and your device’s camera to simplify your life!
Alternatively, if you connect an Android device to your computer, you should be able to run:
$ adb devices
emulator-5554 device
to list all your connected Android devices and emulators. (In my case, only the emulator is “connected”.)
It is then possible to install the .apk
file by running:
$ adb -s emulator-5554 install ~/expo-apps/@anonymous\\ExampleApplication-9be976cea1fb4651a6fa04d8432873eb-signed.apk
Performing Streamed Install
Success
6.2. on iOS
Caveat: I could not fully test this section, so my apologies if this does not work as intended.
Since you should have Xcode, the most promising solution is in this guide. It also mentions other ways to install the .ipa
file.
That’s it for now, thank you for reading this far. You will find below the resources mentioned in this article.
If you have any questions or comments, please drop me a line on Twitter. I shall reply as soon as possible.
Stay tuned for more.
Resources
- Expo: https://expo.io
- Turtle CLI: https://github.com/expo/turtle#readme
- Configuring
app.json
: https://docs.expo.io/versions/latest/workflow/configuration - Expo docs to configure CI with Turtle CLI: https://docs.expo.io/versions/latest/distribution/turtle-cli
- Expo docs to host application on your own servers: https://docs.expo.io/versions/latest/distribution/hosting-your-app/
- How to create a keystore for Android: https://developer.android.com/studio/publish/app-signing#generate-key
- Supporting GitHub repository: https://github.com/RobinCsl/build-standalone-expo-app/
Personal blog written by Robin Cussol
I like math and I like code. Oh, and writing too.