Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 1 addition & 21 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.github.triplet.gradle.androidpublisher.ReleaseStatus
import java.io.FileInputStream
import java.util.Properties
import org.gradle.internal.extensions.stdlib.capitalized
import utilities.BuildTypes
import utilities.FlavorDimensions
import utilities.Flavors
import utilities.SigningConfigs
import utilities.Variant
import utilities.allPlayDebugReleaseVariants
import utilities.appVersionProvider
Expand Down Expand Up @@ -40,12 +37,6 @@ val changelogAssetsDirectory = "$repoRootPath/android/src/main/play/release-note
val rustJniLibsDir = layout.buildDirectory.dir("rustJniLibs/android").get()

val credentialsPath = "${rootProject.projectDir}/credentials"
val keystorePropertiesFile = file("$credentialsPath/keystore.properties")
val keystoreProperties = Properties()

if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

val appVersion = appVersionProvider.get()

Expand Down Expand Up @@ -98,20 +89,9 @@ android {
generateLocaleConfig = false
}

if (keystorePropertiesFile.exists()) {
signingConfigs {
create(SigningConfigs.RELEASE) {
storeFile = file("$credentialsPath/app-keys.jks")
storePassword = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
}
}
}

buildTypes {
getByName(BuildTypes.RELEASE) {
signingConfig = signingConfigs.findByName(SigningConfigs.RELEASE)
signingConfig = null
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
Expand Down
26 changes: 0 additions & 26 deletions android/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ GRADLE_BUILD_TYPE="release"
GRADLE_TASKS=(createOssProdReleaseDistApk createPlayProdReleaseDistApk)
BUILD_BUNDLE="no"
BUNDLE_TASKS=(createPlayProdReleaseDistBundle)
RUN_PLAY_PUBLISH_TASKS="no"
PLAY_PUBLISH_TASKS=()

while [ -n "${1:-""}" ]; do
if [[ "${1:-""}" == "--dev-build" ]]; then
Expand All @@ -23,8 +21,6 @@ while [ -n "${1:-""}" ]; do
BUNDLE_TASKS=(createOssProdFdroidDistBundle)
elif [[ "${1:-""}" == "--app-bundle" ]]; then
BUILD_BUNDLE="yes"
elif [[ "${1:-""}" == "--enable-play-publishing" ]]; then
RUN_PLAY_PUBLISH_TASKS="yes"
fi

shift 1
Expand All @@ -39,12 +35,6 @@ function assert_clean_working_directory {

if [[ "$GRADLE_BUILD_TYPE" == "release" ]]; then
assert_clean_working_directory

if [ ! -f "$SCRIPT_DIR/credentials/keystore.properties" ]; then
echo "ERROR: No keystore.properties file found" >&2
echo " Please configure the signing keys as described in the README" >&2
exit 1
fi
fi

echo "Computing build version..."
Expand All @@ -64,18 +54,6 @@ if [[ "$GRADLE_BUILD_TYPE" == "release" ]]; then
createPlayStagemoleReleaseDistBundle
)
fi

if [[ "$PRODUCT_VERSION" != *"-dev-"* ]]; then
PLAY_PUBLISH_TASKS+=(
publishPlayProdReleaseBundle
)
if [[ "$PRODUCT_VERSION" == *"-alpha"* ]]; then
PLAY_PUBLISH_TASKS+=(
publishPlayDevmoleReleaseBundle
publishPlayStagemoleReleaseBundle
)
fi
fi
fi

# Fallback to the system-wide gradle command if the gradlew script is removed.
Expand Down Expand Up @@ -106,10 +84,6 @@ if [[ "$GRADLE_BUILD_TYPE" == "release" ]]; then
assert_clean_working_directory
fi

if [[ "$RUN_PLAY_PUBLISH_TASKS" == "yes" && "${#PLAY_PUBLISH_TASKS[@]}" -ne 0 ]]; then
$GRADLE_CMD --console plain "${PLAY_PUBLISH_TASKS[@]}"
fi

echo "**********************************"
echo ""
echo " The build finished successfully! "
Expand Down
32 changes: 12 additions & 20 deletions android/docs/BuildInstructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,14 @@ Run the following command to trigger a full debug build:

### Release build
1. Configure a signing key by following [these instructions](#configure-signing-key).
2. Run the following command after setting the `ANDROID_CREDENTIALS_DIR` environment variable to the
directory configured in step 1:
2. Run the following command to build:
```bash
../building/containerized-build.sh android --app-bundle
```
3. Sign the release artifacts using apksigner with the following command:
```bash
apksigner sign --ks app-keys.jks (MullvadVPN)(version)(.apk|.aab)
```

## Build without the provided container

Expand Down Expand Up @@ -153,11 +156,14 @@ Run the following command to build a debug build:

### Release build
1. Configure a signing key by following [these instructions](#configure-signing-key).
2. Move, copy or symlink the directory from step 1 to [./credentials/](./credentials/) (`<repository>/android/credentials/`).
3. Run the following command to build:
2. Run the following command to build:
```bash
../android/build.sh --app-bundle
```
3. Sign the release artifacts using apksigner with the following command:
```bash
apksigner sign --ks app-keys.jks (MullvadVPN)(version)(.apk|.aab)
```

## Build using nix devshell
This is supported on Linux (x86_64) as well as macOS (x86_64 and aarch64).
Expand All @@ -182,24 +188,10 @@ This is supported on Linux (x86_64) as well as macOS (x86_64 and aarch64).
```

## Configure signing key
1. Create a directory to store the signing key, keystore and its configuration:
```
export ANDROID_CREDENTIALS_DIR=/tmp/credentials
mkdir -p $ANDROID_CREDENTIALS_DIR
```

2. Generate a key/keystore named `app-keys.jks` in `ANDROID_CREDENTIALS_DIR` and make sure to write
Generate a key/keystore named `app-keys.jks` and make sure to write
down the used passwords:
```
keytool -genkey -v -keystore $ANDROID_CREDENTIALS_DIR/app-keys.jks -alias release -keyalg RSA -keysize 4096 -validity 10000
```

3. Create a file named `keystore.properties` in `ANDROID_CREDENTIALS_DIR`. Enter the following, but
replace `key-password` and `keystore-password` with the values from step 2:
```bash
keyAlias = release
keyPassword = key-password
storePassword = keystore-password
keytool -genkey -v -keystore app-keys.jks -alias release -keyalg RSA -keysize 4096 -validity 10000
```

## Creating an alpha release
Expand Down
64 changes: 63 additions & 1 deletion ci/buildserver-build-android.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@ BUILD_DIR="$SCRIPT_DIR/mullvadvpn-app"
LAST_BUILT_DIR="$SCRIPT_DIR/last-built"
UPLOAD_DIR="/home/upload/upload"
ANDROID_CREDENTIALS_DIR="$SCRIPT_DIR/credentials-android"
APKSIGNER_CMD="${APKSIGNER_CMD:-apksigner}"
SIGNING_CERTIFICATE_LINEAGE="$BUILD_DIR/ci/android-signing-config/SigningCertificateLineage"
PROVIDER_ARG="$BUILD_DIR/ci/android-signing-config/provider-arg.cfg"
KEY_ALIAS="Certificate for PIV Authentication"

BRANCHES_TO_BUILD=("origin/main")
TAG_PATTERN_TO_BUILD="^android/"

if [[ -z ${YUBIKEY_PIN-} ]]; then
read -rsp "YUBIKEY_PIN = " YUBIKEY_PIN
echo ""
export YUBIKEY_PIN
fi

function upload {
version=$1

Expand All @@ -38,7 +48,7 @@ function build {
ANDROID_CREDENTIALS_DIR=$ANDROID_CREDENTIALS_DIR \
CARGO_TARGET_VOLUME_NAME="cargo-target-android" \
CARGO_REGISTRY_VOLUME_NAME="cargo-registry-android" \
./building/containerized-build.sh android --app-bundle --enable-play-publishing || return 1
./building/containerized-build.sh android --app-bundle || return 1

mv dist/*.{aab,apk} "$artifact_dir" || return 1
}
Expand Down Expand Up @@ -110,6 +120,22 @@ function build_ref {
version="$version$version_suffix"
fi

sign_artifacts "$artifact_dir"

# Upload files to google play, this needs to be done after the artifacts have been signed
# Due to to the upload task only being able to upload all files in a folder we need to copy
# the file to a specific folder every time
local play_upload_dir="$artifact_dir/play_upload"
mkdir -p "$play_upload_dir"
if [[ "$version" != *"-dev-"* ]]; then
upload_google_play "publishPlayProdReleaseBundle" "MullvadVPN-$version.play.aab" "$play_upload_dir"
if [[ "$version" == *"-alpha"* ]]; then
upload_google_play "publishPlayDevmoleReleaseBundle" "MullvadVPN-$version.play.devmole.aab" "$play_upload_dir"
upload_google_play "publishPlayStagemoleReleaseBundle" "MullvadVPN-$version.play.stagemole.aab" "$play_upload_dir"
fi
fi
rm -rf "$play_upload_dir"

(cd "$artifact_dir" && upload "$version") || return 1
# shellcheck disable=SC2216
yes | rm -r "$artifact_dir"
Expand All @@ -121,6 +147,42 @@ function build_ref {
echo ""
}

function sign_artifacts {
dir=$1

pushd "$dir"
# Sign all apk files with the old and new key
for apk in MullvadVPN-*.apk; do
echo "$YUBIKEY_PIN" | $APKSIGNER_CMD -J-add-exports="jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED" sign \
--ks "$ANDROID_CREDENTIALS_DIR/app-keys.jks" \
--ks-pass "file:$ANDROID_CREDENTIALS_DIR/keystore.properties.new" \
--next-signer --ks NONE --ks-type PKCS11 --ks-key-alias "$KEY_ALIAS" \
--provider-class sun.security.pkcs11.SunPKCS11 --provider-arg "$PROVIDER_ARG" \
--lineage "$SIGNING_CERTIFICATE_LINEAGE" --rotation-min-sdk-version 28 --in "$apk"
done

# Sign all aab files with the upload key (new key)
for aab in MullvadVPN-*.aab
do
echo "$YUBIKEY_PIN" | $APKSIGNER_CMD --J-add-exports="jdk.crypto.cryptoki/sun.security.pkcs11=ALL-UNNAMED" sign \
--ks NONE --ks-type PKCS11 --ks-key-alias "$KEY_ALIAS" \
--provider-class sun.security.pkcs11.SunPKCS11 --provider-arg "$PROVIDER_ARG" \
--in "$aab"
done
popd
}

function upload_google_play {
task=$1
file=$2
upload_dir=$3

rm -r "${upload_dir:?}/*"
cp "$file" "$upload_dir/"

./building/container-run.sh android ./android/gradlew -p android "$task" --artifact-dir "$upload_dir"
}

cd "$BUILD_DIR"

while true; do
Expand Down
Loading