Skip to content
Merged
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
6 changes: 5 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
"GRADLE_USER_HOME": "/home/vscode/.gradle"
},
"remoteEnv": {
"AUTOSPEED_PERSONAL_USE_ACK": "${localEnv:AUTOSPEED_PERSONAL_USE_ACK}"
"AUTOSPEED_PERSONAL_USE_ACK": "${localEnv:AUTOSPEED_PERSONAL_USE_ACK}",
"AUTOSPEED_SIGNING_STORE_FILE": "${localEnv:AUTOSPEED_SIGNING_STORE_FILE}",
"AUTOSPEED_SIGNING_STORE_PASSWORD": "${localEnv:AUTOSPEED_SIGNING_STORE_PASSWORD}",
"AUTOSPEED_SIGNING_KEY_ALIAS": "${localEnv:AUTOSPEED_SIGNING_KEY_ALIAS}",
"AUTOSPEED_SIGNING_KEY_PASSWORD": "${localEnv:AUTOSPEED_SIGNING_KEY_PASSWORD}"
},
"mounts": [
"source=autospeed-gradle-cache,target=/home/vscode/.gradle,type=volume",
Expand Down
42 changes: 42 additions & 0 deletions docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,48 @@ also permits key rotation later. Verify a build with:

and confirm the reported certificate SHA-256 matches the keystore.

## Local release builds

The workflow above is the only thing that publishes a release, but a signed,
release-quality APK can also be built locally. This is the only way to obtain a
personal-profile artifact, which the workflow never publishes.

`devcontainer.json` forwards the signing variables from the host environment
into the container, so export them in the shell rather than passing them as
`--remote-env` arguments, where they would be visible in the host process list:

```bash
export AUTOSPEED_SIGNING_STORE_FILE=/workspaces/autospeed/.release-secrets/autospeed-release.jks
export AUTOSPEED_SIGNING_KEY_ALIAS=<alias>
read -rs -p 'store password: ' AUTOSPEED_SIGNING_STORE_PASSWORD && export AUTOSPEED_SIGNING_STORE_PASSWORD
read -rs -p 'key password: ' AUTOSPEED_SIGNING_KEY_PASSWORD && export AUTOSPEED_SIGNING_KEY_PASSWORD
```

`AUTOSPEED_SIGNING_STORE_FILE` is a path *inside* the container, so the
keystore has to sit somewhere the workspace mount exposes. `.release-secrets/`
is git-ignored for exactly this, and a keystore kept outside the repository can
be copied there for the build.

A personal-profile release additionally requires the design section 2.1
acknowledgment, which is also forwarded from the host:

```bash
export AUTOSPEED_PERSONAL_USE_ACK='Do not configure or interact with Autospeed while driving. Obey applicable laws and remain attentive.'
./scripts/container-run ./scripts/build-apk personalPlayRelease
```

Omitting the signing variables does not fail the build: the signing config is
simply never created and AGP emits `*-release-unsigned.apk`, which cannot be
installed (`INSTALL_PARSE_FAILED_NO_CERTIFICATES`). `scripts/build-apk`
therefore verifies release output with `apksigner` and fails instead of copying
an unusable APK into `dist/` under a name that looks finished.

Unless `AUTOSPEED_VERSION_NAME` and `AUTOSPEED_VERSION_CODE` are set, a local
build carries the development version (`0.1.0` / `1`). Personal and public
profiles use different application IDs, so a locally built personal APK and a
published public release are separate installations and never update over one
another.

## Publish

After checks pass on `main`, create and push an annotated tag:
Expand Down
21 changes: 21 additions & 0 deletions scripts/build-apk
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ if [[ -z "${apk}" ]]; then
exit 1
fi

# A release build with no signing configuration still succeeds: AGP simply emits
# `*-release-unsigned.apk`, which this script would then copy to a destination name that gives no
# hint it cannot be installed. Attempting to install it fails much later with
# INSTALL_PARSE_FAILED_NO_CERTIFICATES, so confirm the signature here rather than trusting that a
# successful build implies a usable artifact.
if [[ "${BUILD_TYPE}" == "release" ]]; then
apksigner="$(find "${ANDROID_HOME:-/opt/android-sdk}/build-tools" -type f -name apksigner 2>/dev/null | sort -V | tail -1)"
if [[ -z "${apksigner}" ]]; then
echo "Cannot locate apksigner, so ${apk} cannot be confirmed as signed." >&2
exit 1
fi
if ! "${apksigner}" verify "${apk}" >/dev/null 2>&1; then
echo "${apk} is not signed and cannot be installed." >&2
echo "Export AUTOSPEED_SIGNING_STORE_FILE, AUTOSPEED_SIGNING_STORE_PASSWORD," >&2
echo "AUTOSPEED_SIGNING_KEY_ALIAS and AUTOSPEED_SIGNING_KEY_PASSWORD on the host" >&2
echo "(devcontainer.json forwards them into the container) and build again." >&2
echo "See docs/releases.md for the local release-build recipe." >&2
exit 1
fi
fi

mkdir -p "${DIST_DIR}"
readonly DEST="${DIST_DIR}/autospeed-${FLAVOR}-${BUILD_TYPE}.apk"
install -m 0644 "${apk}" "${DEST}"
Expand Down