Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please provide verifiable builds #1

Open
Giszmo opened this issue Dec 28, 2019 · 15 comments
Open

Please provide verifiable builds #1

Giszmo opened this issue Dec 28, 2019 · 15 comments
Assignees
Labels
documentation Improvements or additions to documentation

Comments

@Giszmo
Copy link

Giszmo commented Dec 28, 2019

I tried to build your wallet but failed. Could you look into the issues found and make it easier to reproduce your release builds? Above link has an in detail description of what I tried. For deterministic builds it is usually best to work with docker containers, so that as little parameters as possible are left to random chance.

@Turtus
Copy link
Member

Turtus commented Dec 28, 2019

container with both android and xcode sdk is not very easy to use - actually we are pushing here only stable releases from our internal repository - cose so many work and changes every day - but @rhrusha will answer more about build

@Giszmo
Copy link
Author

Giszmo commented Dec 29, 2019

Looking forward to public verifiability. Especially in open source wallets a documentation for build verification is the most convincing statement that build verification is also done internally, which is crucial to avoid single engineers in distress doing catastrophic things.

@rhrusha
Copy link
Member

rhrusha commented Jan 14, 2020

Deterministic builds it's our goal and we moving in this way. Shortly we will add build instructions for Android and IOS. Then we will be updating instructions step-by-step to reach the true deterministic build.

@rhrusha rhrusha self-assigned this Jan 20, 2020
@rhrusha rhrusha added the documentation Improvements or additions to documentation label Jan 20, 2020
@rhrusha
Copy link
Member

rhrusha commented Jan 23, 2020

@Giszmo we have updated repository with build instructions for Android. We do not provide yet deterministic builds, there is only verifiable build instruction that anyone can use to build Android APK which can be installed to android device. IOS instructions is coming.

@Giszmo
Copy link
Author

Giszmo commented Jan 24, 2020

Things look slightly better but verification fails due to the correct revision not being available. Please let us know once the correct version is tagged. Also please read our updated review.

@Giszmo
Copy link
Author

Giszmo commented Mar 29, 2020

Is there any progress? Should we re-evaluate?

@Giszmo
Copy link
Author

Giszmo commented Oct 1, 2020

Same question again, half a year later ...

@rhrusha
Copy link
Member

rhrusha commented Oct 12, 2020

We focusing now on resolving this issue with verifiable builds.
I will update you when we get a result.

@rhrusha
Copy link
Member

rhrusha commented Oct 27, 2020

Hi Leo,

Based on your method of build verification we developed instructions how to build and verify our application.
There is verifiable builds instruction

Waiting for your feedback.

@Giszmo
Copy link
Author

Giszmo commented Oct 30, 2020

The instructions start with git clone https://gl.blocksoftlab.com/Trustee/trusteewallet.git but it then asks for credentials, yet there is not even a "create account" button on https://gl.blocksoftlab.com/users/sign_in

(I would love to see a non-GitHub repo and yet better a self-hosted GitLab but ideally cloning requires no credentials at all.)

Else, the claim that Google adds stuff is strange to me. Are you using App Bundles?

To sweep all the rest of the diffs under the rug, claiming:

The rest of files referring to smali_classes* seems have some sort of decompilation artefacts.

is not very convincing. If you don't understand exactly what those diffs are, how can you be certain they are not malicious?

Without trying things myself I see no way this could pass as reproducible.

Lines like < .line 316 are debug symbols, not "decompilation artefacts". Those should not affect the runtime behavior of the app but < const-class v0, Lokhttp3/internal/cache/DiskLruCache; is not a debug symbol or "decompilation artifact".

I have no idea if there is a way to reproduce apps distributed using app bundles but without some canonical tool that reliably reproduces binaries app-bundle based apps might very well generally not be reproducible until Google comes around and fixes it.

@Giszmo
Copy link
Author

Giszmo commented Oct 30, 2020

So following the instruction using the repo on GitHub gets me this error:

leo@codex:/tmp$ cd trusteeWallet/
leo@codex:/tmp/trusteeWallet(master)$ cat docker/verify_android_build.sh 
#!/usr/bin/env sh

docker build --build-arg BUILD_NUMBER=631 --build-arg COMMIT_SHA=b684f368 -t android/verify -f ./docker/Dockerfile.verifyandroidbuild .

leo@codex:/tmp/trusteeWallet(master)$ ll docker/
total 20K
drwxrwxr-x  2 leo leo 4.0K Oct 29 23:09 .
drwxrwxr-x 11 leo leo 4.0K Oct 29 23:09 ..
-rw-rw-r--  1 leo leo 2.1K Oct 29 23:09 Dockerfile.androidprebuild
-rw-rw-r--  1 leo leo 1.9K Oct 29 23:09 Dockerfile.verifyandroidbuild
-rwxrwxr-x  1 leo leo  156 Oct 29 23:09 verify_android_build.sh
leo@codex:/tmp/trusteeWallet(master)$ docker/verify_android_build.sh
Sending build context to Docker daemon  26.65MB
Step 1/10 : FROM trusteewallet/androidprebuild
latest: Pulling from trusteewallet/androidprebuild
3f2411103a12: Already exists 
4da04088b2c2: Already exists 
ab1184837b6f: Already exists 
354c6da61dcc: Already exists 
551336c35f06: Pull complete 
02f5a9a701a4: Pull complete 
ce0ed1001702: Pull complete 
9fec0bb04037: Pull complete 
3bf87f98bb92: Pull complete 
75dc35123306: Pull complete 
1635a887cf14: Pull complete 
Digest: sha256:342db68890c773775db2b969bdc078493f7ebc549c042b7716f44e91dd129273
Status: Downloaded newer image for trusteewallet/androidprebuild:latest
 ---> c983a0c19548
Step 2/10 : ARG BUILD_NUMBER=1
 ---> Running in 755f7f09d926
Removing intermediate container 755f7f09d926
 ---> 951bf6b1858e
Step 3/10 : ARG COMMIT_SHA=12345678
 ---> Running in 5cfa5cf7f260
Removing intermediate container 5cfa5cf7f260
 ---> 8099fc4d94cc
Step 4/10 : RUN sdkmanager --install "build-tools;29.0.3" &&     sdkmanager --install "platform-tools" &&     sdkmanager --install "platforms;android-29" &&     sdkmanager --install "ndk-bundle" "ndk;21.3.6528147" &&     sdkmanager --install "cmake;3.6.4111459"
 ---> Running in c7c5c5c1b393
[=======================================] 100% Unzipping... android-10/lib64/lib
[=======================================] 100% Computing updates...             
[=======================================] 100% Unzipping... android-10/framework
[=======================================] 100% Unzipping... android-ndk-r21d/wra
[=======================================] 100% Unzipping... source.properties   
Removing intermediate container c7c5c5c1b393
 ---> c53c733f396a
Step 5/10 : RUN cd ./src &&     git clone https://github.com/trustee-wallet/trusteeWallet.git . &&     sed -i "s/android.builder.sdkDownload=true/android.builder.sdkDownload=false/g" ./android/gradle.properties &&     sed -i "s/versionCode 1/versionCode $BUILD_NUMBER/g" ./android/app/build.gradle &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/config.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/config.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.prod.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.prod.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.tester.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.tester.js
 ---> Running in bd8cd83ba073
fatal: destination path '.' already exists and is not an empty directory.
The command '/bin/sh -c cd ./src &&     git clone https://github.com/trustee-wallet/trusteeWallet.git . &&     sed -i "s/android.builder.sdkDownload=true/android.builder.sdkDownload=false/g" ./android/gradle.properties &&     sed -i "s/versionCode 1/versionCode $BUILD_NUMBER/g" ./android/app/build.gradle &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/config.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/config.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.prod.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.prod.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.tester.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.tester.js' returned a non-zero code: 128

@Giszmo
Copy link
Author

Giszmo commented Oct 30, 2020

The script is not very robust in that it doesn't delete potentially stale data when running again. The relevant docker containers should probably not be used from cache. At least not all:

leo@codex:/tmp/trusteeWallet(master)$ git checkout v1.40.631
Note: switching to 'v1.40.631'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 3415325 v1.40.631
leo@codex:/tmp/trusteeWallet((HEAD detached at v1.40.631))$ docker/verify_android_build.sh
Sending build context to Docker daemon  26.64MB
Step 1/10 : FROM trusteewallet/androidprebuild
 ---> c983a0c19548
Step 2/10 : ARG BUILD_NUMBER=1
 ---> Using cache
 ---> 951bf6b1858e
Step 3/10 : ARG COMMIT_SHA=12345678
 ---> Using cache
 ---> 8099fc4d94cc
Step 4/10 : RUN sdkmanager --install "build-tools;29.0.3" &&     sdkmanager --install "platform-tools" &&     sdkmanager --install "platforms;android-29" &&     sdkmanager --install "ndk-bundle" "ndk;21.3.6528147" &&     sdkmanager --install "cmake;3.6.4111459"
 ---> Using cache
 ---> c53c733f396a
Step 5/10 : RUN cd ./src &&     git clone https://github.com/trustee-wallet/trusteeWallet.git . &&     sed -i "s/android.builder.sdkDownload=true/android.builder.sdkDownload=false/g" ./android/gradle.properties &&     sed -i "s/versionCode 1/versionCode $BUILD_NUMBER/g" ./android/app/build.gradle &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/config.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/config.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.prod.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.prod.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.tester.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.tester.js
 ---> Running in bc5875691003
fatal: destination path '.' already exists and is not an empty directory.
The command '/bin/sh -c cd ./src &&     git clone https://github.com/trustee-wallet/trusteeWallet.git . &&     sed -i "s/android.builder.sdkDownload=true/android.builder.sdkDownload=false/g" ./android/gradle.properties &&     sed -i "s/versionCode 1/versionCode $BUILD_NUMBER/g" ./android/app/build.gradle &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/config.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/config.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.prod.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.prod.js &&     sed -i "s/VERSION_CODE_PLACEHOLDER/$BUILD_NUMBER/g" ./app/config/changeable.tester.js &&     sed -i "s/COMMIT_SHORT_SHA_PLACEHOLDER/$COMMIT_SHA/g" ./app/config/changeable.tester.js' returned a non-zero code: 128

@rhrusha
Copy link
Member

rhrusha commented Nov 2, 2020

Hi Leo,

The instructions start with git clone https://gl.blocksoftlab.com/Trustee/trusteewallet.git but it then asks for credentials
There should be a link to Github. Already fixed this wrong link.

We are reviewing the rest related with building process and then will get back to you.

@Giszmo
Copy link
Author

Giszmo commented Sep 30, 2023

So I tried to reproduce this product again. First of all I was missing a tag for what I got from Google Play - "1.51.6" - but I ran into more issues and ultimately gave up on building.

Please update the build instructions.

Please explain how to invoke docker/verify_android_build.sh as to me it looks like documentation and not an actual script.

Here is my full log, soon to be published on walletscrutiny:

Update 2023-09-29: We have not had a look in a long time. While the last
time we were invited to re-evaluate, it didn't look too good, lets see if things
got worse or better ...

On Google Play we downloaded version 1.51.6. On GitHub though we can only find
up to version v1.51.5 in the tags. The latest code though might simply miss a
tag as this line
looks good.

So ... the build instructions say:

All building steps are tested with Ubuntu 16.04

That version of Ubuntu would still be supported under the
"Expanded Security Maintenance" but ... it's really
old.

nodejs version 10.x

Installing this we get a warning: "Node.js 10.x is no longer actively supported! [...] You should migrate to a supported version of Node.js as soon as possible."

So we assume the build instructions are not up to date ...

... and node developers are really trying to dissuade us from installing this
old version by forcing us to wait first 20s and then 60s ...

$ podman run --rm -it ubuntu:16.04 bash
root@8b8a40242a85:/# apt update
root@8b8a40242a85:/# apt install -y curl
root@8b8a40242a85:/# curl -sL https://deb.nodesource.com/setup_10.x | bash -
root@8b8a40242a85:/# apt install -y build-essential git openjdk-8-jdk nodejs gcc g++ make
root@8b8a40242a85:/# echo "JAVA_HOME=$(which java)" | tee -a /etc/environment
root@8b8a40242a85:/# source /etc/environment
root@8b8a40242a85:/# echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf && sysctl -p
root@8b8a40242a85:/# mkdir ~/androidsdk
root@8b8a40242a85:/# export ANDROID_HOME=~/androidsdk
root@8b8a40242a85:/# mkdir ~/androidsdk/licenses 
root@8b8a40242a85:/# echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" >  ~/androidsdk/licenses/android-sdk-license
root@8b8a40242a85:/# git clone https://github.com/trustee-wallet/trusteeWallet.git
root@8b8a40242a85:/# cd ./trusteeWallet
root@8b8a40242a85:/trusteeWallet# npm install
root@8b8a40242a85:/trusteeWallet# npx jetifier
root@8b8a40242a85:/trusteeWallet# rm -f shim.js
root@8b8a40242a85:/trusteeWallet# ./node_modules/.bin/rn-nodeify --hack --install
root@8b8a40242a85:/trusteeWallet/android# cd ./android
root@8b8a40242a85:/trusteeWallet/android# ./gradlew assembleRelease
> Task :app:bundleReleaseJsAndAssets
Loading dependency graph, done.
error SyntaxError: /trusteeWallet/app/router/NewRouter.js: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>? (326:8)

  324 |                 />
  325 |             
> 326 |         </Tab.Navigator>
      |         ^
SyntaxError: /trusteeWallet/app/router/NewRouter.js: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>? (326:8)
  327 |     )

  328 | }
  329 |. Run CLI with --verbose flag for more details.
  324 |                 />
  325 |             
> 326 |         </Tab.Navigator>
      |         ^
  327 |     )
  328 | }
  329 |
    at constructor (/trusteeWallet/node_modules/@babel/parser/lib/index.js:356:19)
    at FlowParserMixin.raise (/trusteeWallet/node_modules/@babel/parser/lib/index.js:3223:19)
    at FlowParserMixin.jsxParseElementAt (/trusteeWallet/node_modules/@babel/parser/lib/index.js:6911:18)
...

so this might be due to the build instructions being outdated, using old tools.

But how about theAndroid verifiable builds section?
Maybe that works better?

$ git clone https://github.com/trustee-wallet/trusteeWallet.git
$ cd trusteeWallet/
$ cat docker/verify_android_build.sh 
#!/usr/bin/env sh

docker build --build-arg BUILD_NUMBER=VERSION_CODE_PLACEHOLDER --build-arg COMMIT_SHA=COMMIT_SHORT_SHA_PLACEHOLDER -t android/verify -f ./docker/Dockerfile.verifyandroidbuild .

That looks benign but also like it could use some parameters ...

$ docker/verify_android_build.sh 
Sending build context to Docker daemon  38.37MB
Step 1/14 : FROM trusteewallet/androidprebuild
latest: Pulling from trusteewallet/androidprebuild

That's already a problem. We'd have to build trusteewallet/androidprebuild to
avoid testing the provider's work using the provider's own binaries ...

$ docker image rm trusteewallet/androidprebuild:latest
$ docker image prune 
$ docker build -t trusteewallet/androidprebuild -f ./docker/Dockerfile.androidprebuild .

Ok, this builds on Ubuntu 20.04, not like the build instructions above on 16.04.
Promising ...

Node still is being installed deprecated scripts:

================================================================================
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
================================================================================

                           SCRIPT DEPRECATION WARNING                    

  
  This script, located at https://deb.nodesource.com/setup_X, used to
  install Node.js is deprecated now and will eventually be made inactive.

  Please visit the NodeSource distributions Github and follow the
  instructions to migrate your repo.
  https://github.com/nodesource/distributions

  The NodeSource Node.js Linux distributions GitHub repository contains
  information about which versions of Node.js and which Linux distributions
  are supported and how to install it.
  https://github.com/nodesource/distributions


                          SCRIPT DEPRECATION WARNING

================================================================================
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
================================================================================

TO AVOID THIS WAIT MIGRATE THE SCRIPT
Continuing in 60 seconds (press Ctrl-C to abort) ...

But we get our local image from source and can continue ...

...
Successfully tagged trusteewallet/androidprebuild:latest
$ docker/verify_android_build.sh 
...
[4/4] Building fresh packages...
warning Error running install script for optional dependency: "/trustee/src/node_modules/sodium-native: Command failed.
Exit code: 1
Command: node-gyp-build \"node preinstall.js\" \"node postinstall.js\"
Arguments: 
Directory: /trustee/src/node_modules/sodium-native
Output:
libtool is required, but wasn't found on this system
./configure: 5: ./configure: not found
/trustee/src/node_modules/sodium-native/preinstall.js:119
    if (err) throw err
             ^

Error: ./configure exited with 127
    at ChildProcess.<anonymous> (/trustee/src/node_modules/sodium-native/preinstall.js:149:25)
    at ChildProcess.emit (node:events:513:28)
    at Process.ChildProcess._handle.onexit (node:internal/child_process:293:12)"
info This module is OPTIONAL, you can safely ignore this error
...
FAILURE: Build failed with an exception.

* Where:
Build file '/trustee/src/android/app/build.gradle' line: 136

* What went wrong:
A problem occurred evaluating project ':app'.
> Could not get unknown property 'VERSION_CODE_PLACEHOLDER' for DefaultConfig_Decorated{name=main, dimension=null, minSdkVersion=DefaultApiVersion{mApiLevel=21, mCodename='null'}, targetSdkVersion=DefaultApiVersion{mApiLevel=31, mCodename='null'}, renderscriptTargetApi=null, renderscriptSupportModeEnabled=null, renderscriptSupportModeBlasEnabled=null, renderscriptNdkModeEnabled=null, versionCode=null, versionName=null, applicationId=com.trusteewallet, testApplicationId=null, testInstrumentationRunner=null, testInstrumentationRunnerArguments={}, testHandleProfiling=null, testFunctionalTest=null, signingConfig=null, resConfig=[], buildConfigFields={}, resValues={}, proguardFiles=[], consumerProguardFiles=[], manifestPlaceholders={}, wearAppUnbundled=null} of type com.android.build.gradle.internal.dsl.DefaultConfig.

So as mentioned above, the lacking arguments are indeed a problem. Let's try the
command with only a commit sha:

$ cat docker/verify_android_build.sh 
#!/usr/bin/env sh

docker build --build-arg BUILD_NUMBER=VERSION_CODE_PLACEHOLDER --build-arg COMMIT_SHA=COMMIT_SHORT_SHA_PLACEHOLDER -t android/verify -f ./docker/Dockerfile.verifyandroidbuild .
$ git log -n 1
commit 721e0c7749dfdc9534f04287b3f09b3a5f61931a (HEAD -> master, origin/master, origin/HEAD)
...
$ docker build --build-arg COMMIT_SHA=721e0c7749dfdc9534f04287b3f09b3a5f61931a -t android/verify -f ./docker/Dockerfile.verifyandroidbuild .
...
SyntaxError: /trustee/src/app/router/NewRouter.js: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>? (326:8)
...
BUILD FAILED in 6m 35s

So ... we got the error from before again and conclude, the current version is
not verifiable.

@keraliss
Copy link

keraliss commented Aug 7, 2024

Hi, this is keraliss from walletscrutiny, we tried to build the wallet using the dockerfile provided in the repository, and got this error

gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack at ChildProcess.<anonymous> (/usr/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:209:23)
gyp ERR! System Linux 6.1.0-20-amd64
gyp ERR! command "/usr/bin/node" "/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /trustee/src/node_modules/sha3
gyp ERR! node -v v18.20.2
gyp ERR! node-gyp -v v10.0.1
gyp ERR! not ok
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
The command '/bin/sh -c cd ./src && yarn install --no-progress --frozen-lockfile' returned a non-zero code: 1

As per our understanding, The build error stems from a failure during the make process for the sha3 module when running yarn install. This issue prevents the successful build of the Docker image and requires resolution.

Can someone please help on resolving this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

5 participants
@Giszmo @Turtus @rhrusha @keraliss and others