You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Goal: every v* tag builds a signed Android App Bundle (AAB) and uploads it to
the Google Play internal test track under the Dronecode Foundation account.
The GitHub release APK becomes signed with the same upload key. The
application id stays com.px4.hawkeye.android and becomes permanent at the
first Play upload. The first upload must be done by hand because the Play
Developer API cannot create a new app.
Background for anyone picking up an item: releases are cut by pushing a v*
tag, which runs .github/workflows/release.yml. Its android-apk job builds
with ./gradlew assembleRelease -PhawkeyeVersionName=<version> (gradlew is
in android/, not the repo root) and attaches
hawkeye--android-unsigned.apk to the GitHub release. versionCode is
derived in android/app/build.gradle.kts as major1000000 + minor1000 +
patch, which makes v0.4.0-rc1 collide with v0.4.0 (documented in
docs/developer/releasing.md). There is no signing config, keystore, or AAB
anywhere in the repo today. New Play apps must upload an AAB and are enrolled
in Play App Signing: Google holds the app signing key, we hold an upload key.
Highest shipped versionCode so far is 3000 (v0.3.0).
Console and secrets (manual, needs Dronecode Play account access)
Mint the upload keystore and back it up.
Run: keytool -genkeypair -v -keystore hawkeye-upload.jks -storetype PKCS12 -alias hawkeye-upload -keyalg RSA -keysize 4096 -validity 10950 -dname "CN=Hawkeye, O=Dronecode Foundation"
PKCS12 keystores use a single password for store and key. Record the
certificate fingerprint with keytool -list -v -keystore hawkeye-upload.jks -alias hawkeye-upload | grep SHA256.
Store the keystore file, password, alias, fingerprint, and creation date as
one entry in the Dronecode password manager. Note in the entry that Play App
Signing allows an upload key reset through the Play Console, so this backup
is a convenience rather than the last line of defense.
Done when: the entry exists in the password manager and the .jks is deleted
from any shared or synced folder.
Done 2026-08-16: keystore minted (RSA 4096, PKCS12, alias hawkeye-upload,
valid to 2056) with upload certificate SHA-256
DB:DF:32:2A:02:A8:2B:3D:8A:BA:E9:EE:26:1E:6C:EF:D9:C8:16:22:DD:0D:A3:03:B7:4F:A2:B1:A5:D4:89:FE.
Add the three signing secrets to the PX4/Hawkeye repo.
From a machine with the keystore: gh secret set ANDROID_UPLOAD_KEYSTORE_BASE64 --repo PX4/Hawkeye --body "$(base64 -i hawkeye-upload.jks)" gh secret set ANDROID_UPLOAD_KEYSTORE_PASSWORD --repo PX4/Hawkeye (paste the password) gh secret set ANDROID_UPLOAD_KEY_ALIAS --repo PX4/Hawkeye --body hawkeye-upload
Done when: all three appear in the repo secrets list.
Done 2026-08-16: all three secrets set, and a workflow_dispatch run of
android.yml exercised the full signed path with them (keystore decode,
signed build, apksigner and jarsigner verification all passing): https://github.com/PX4/Hawkeye/actions/runs/31952546318
Create the Hawkeye app in the Play Console.
In the Dronecode Foundation Play Console account: All apps, Create app.
Name Hawkeye, type App, Free, default language English. This step cannot be
done through the API.
Done when: the app exists in the console (its package name is fixed later
by the first AAB upload, which must be built with applicationId
com.px4.hawkeye.android).
Set up the internal testing track and tester list.
Play Console: Testing, Internal testing, create an email list (up to 100
testers) and attach it to the track. Internal testing does not require a
store listing, content rating, or data safety form to roll out; if the
console blocks on a specific declaration anyway, complete only what it
demands.
Done when: the internal track shows the tester list attached.
Done 2026-08-16: track is live and testers join through the opt-in link https://play.google.com/apps/internaltest/4700645534848215513
Note for onboarding testers: a tester must be on the email list AND visit
the opt-in link once before the app shows up for them in Play.
Build and upload the first signed AAB by hand.
Prerequisite: the Gradle signing item under Repository changes is merged.
With the keystore locally: export HAWKEYE_UPLOAD_KEYSTORE=/absolute/path/hawkeye-upload.jks export HAWKEYE_UPLOAD_KEYSTORE_PASSWORD=<password> export HAWKEYE_UPLOAD_KEY_ALIAS=hawkeye-upload cd android && ./gradlew bundleRelease -PhawkeyeVersionName=0.4.0-rc1
Upload android/app/build/outputs/bundle/release/app-release.aab to the
internal testing track, accept the Google generated app signing key when
prompted (this enrolls Play App Signing), and roll out. This consumes
versionCode 400001, so the git tag v0.4.0-rc1 must never be pushed;
automation starts at v0.4.0-rc2.
Done when: the internal track shows release 0.4.0-rc1 rolled out and
testers can install it.
Done 2026-08-16: 0.4.0-rc1 (versionCode 400001) uploaded and rolled out to
internal testing; Play App Signing enrolled with a Google generated app
signing key. The git tag v0.4.0-rc1 is therefore permanently reserved and
must never be pushed; automation starts at v0.4.0-rc2.
Create the service account for CI uploads.
In Google Cloud Console, in a Dronecode owned project (create one named
hawkeye-play-publisher if none fits): enable the Google Play Android
Developer API, create a service account (no GCP roles needed), and create a
JSON key for it.
Done when: the JSON key file is downloaded and the API shows enabled.
Grant the service account access in the Play Console.
Play Console: Users and permissions, Invite new user, enter the service
account email. Grant app level access to Hawkeye only, with permissions
View app information (read only) and Release apps to testing tracks.
Nothing for production or app signing.
Known delay: a freshly invited service account can return API permission
errors for up to 24 hours; wait or edit any console setting before
debugging a failed upload.
Done when: the service account is listed under Users and permissions with
those two permissions.
Add the service account secret and clean up. gh secret set PLAY_SERVICE_ACCOUNT_JSON --repo PX4/Hawkeye < service-account.json
Then delete every local copy of the JSON key.
Done when: the secret exists and no local copy of the key remains.
Done 2026-08-16: hawkeye-ci-publisher@api-7299061481369082666-700664
.iam.gserviceaccount.com created in the Play linked GCP project with no GCP
roles, JSON key stored as PLAY_SERVICE_ACCOUNT_JSON and deleted locally,
and the account granted View app information plus Release apps to testing
tracks on Hawkeye only.
Repository changes
Ignore keystores. Add *.jks and *.keystore to the root
.gitignore under the Android section.
Done when: git check-ignore hawkeye-upload.jks matches.
Conditional upload signing in Gradle.
File: android/app/build.gradle.kts. Read env vars via
providers.environmentVariable: HAWKEYE_UPLOAD_KEYSTORE (path to a decoded
.jks), HAWKEYE_UPLOAD_KEYSTORE_PASSWORD, HAWKEYE_UPLOAD_KEY_ALIAS. When the
keystore var is present, create signingConfigs "upload" (keyPassword equals
storePassword, PKCS12) and set it on the release build type; when absent,
change nothing so local, PR, and fork builds keep producing an unsigned
release APK. Note: when signed, the output becomes app-release.apk instead
of app-release-unsigned.apk; the verify script resolves the file through
output-metadata.json so no path fixes are needed.
Done when: a local ./gradlew assembleRelease -PhawkeyeVersionName=0.0.0-dev
without env vars still builds unsigned, and with the three env vars set the
APK passes apksigner verify.
versionCode scheme with a prerelease component.
File: android/app/build.gradle.kts (the hawkeyeVersionCode block).
New formula: major100000000 + minor100000 + patch*100 + rc, where rc is
99 for a final version (no suffix), N for suffix rcN with N in 1..98, and 0
for suffixes dev and ci. Any other suffix fails the build. Require major in
0..20 so the maximum stays below the Play cap of 2100000000. Examples:
0.4.0-rc1 gives 400001, 0.4.0 gives 400099, 1.2.3 gives 100200399. Keep the
existing coerceAtLeast(1) so 0.0.0-dev still yields 1. All new codes exceed
3000 (shipped v0.3.0), so sideload upgrades stay monotonic. Update the
comment block above it.
Done when: the examples above hold (check with aapt dump badging on a built
APK) and an unknown suffix fails the build with a clear message.
Signed mode for the APK verify script.
File: android/scripts/verify-release-apk.sh. Add an optional --signed flag
preserving current behavior without it. With --signed, locate apksigner in
the newest $ANDROID_SDK_ROOT/build-tools/* (fall back to $ANDROID_HOME) and
fail the script if apksigner verify --print-certs fails.
Done when: the script passes on a signed APK with --signed, fails on an
unsigned APK with --signed, and is unchanged without the flag.
New AAB verify script.
New file: android/scripts/verify-release-bundle.sh, matching the style of
verify-release-apk.sh. Given the path to an .aab, assert it exists and that unzip -l lists base/lib/arm64-v8a/libhawkeye.so,
base/lib/x86_64/libhawkeye.so, and the four asset trees base/assets/models,
base/assets/shaders, base/assets/fonts, base/assets/themes. No bundletool
dependency.
Done when: the script passes on a real bundleRelease output and fails when
pointed at an APK or a bundle missing an asset tree.
Exercise signing and bundling in android.yml before any tag.
File: .github/workflows/android.yml, job release-build. Route the keystore
secret through job env (UPLOAD_KEYSTORE_B64 from
secrets.ANDROID_UPLOAD_KEYSTORE_BASE64) because the secrets context is
unavailable in job level if. Add a step guarded by if: env.UPLOAD_KEYSTORE_B64 != '' that decodes it to
$RUNNER_TEMP/upload-keystore.jks and exports HAWKEYE_UPLOAD_KEYSTORE via
GITHUB_ENV. Build with ./gradlew assembleRelease bundleRelease -PhawkeyeVersionName=0.0.0-ci (passwords and alias as step env from
secrets). Verify the APK with --signed when the keystore is present and
without it otherwise, and verify the AAB with the new script. Never upload
to Play from this workflow. Forks without secrets must stay green on the
unsigned path.
Done when: a run on main with secrets shows the signed verify and AAB
verify steps passing, and a PR from a fork still passes.
Done 2026-08-16: the post-merge run on main decoded the keystore and passed
the signed APK and AAB verifications: https://github.com/PX4/Hawkeye/actions/runs/31954206015
Sign, bundle, and upload in release.yml.
File: .github/workflows/release.yml, job android-apk (rename to android,
update its name accordingly). Same keystore decode pattern as android.yml.
Build assembleRelease bundleRelease with -PhawkeyeVersionName from
needs.source-tarball.outputs.version. Verify the APK (--signed when the
keystore is present) and the AAB. Stage the APK as
hawkeye--android.apk when signed, or keep
hawkeye--android-unsigned.apk when not (forks), and upload it to
the GitHub release with gh release upload as today. Remove the stale
comment about dropping the -unsigned suffix. Add a final step, guarded on env.PLAY_SA_JSON != '' (routed from secrets.PLAY_SERVICE_ACCOUNT_JSON via
job env), using the r0adkll/upload-google-play action pinned to a commit
SHA with a version comment: serviceAccountJsonPlainText from the secret,
packageName com.px4.hawkeye.android, releaseFiles pointing at
android/app/build/outputs/bundle/release/app-release.aab, track internal,
status completed. Do not gate on prerelease tags: rc uploads are the point
of the internal track. Do not attach the AAB to the GitHub release.
Done when: workflow lint passes and every new step is guarded so a fork or
a repo without Play secrets falls back to today's behavior.
Docs.
docs/developer/releasing.md: asset name hawkeye--android.apk, new
versionCode formula with an example table (v0.4.0-rc1 gives 400001, v0.4.0
gives 400099, v1.2.3 gives 100200399, no tag gives 1), delete the stale
prerelease collision warning, rewrite the Android APK section (signed with
the upload key, secret names, fork fallback), add a Google Play internal
track section covering what uploads when and the service account setup.
docs/installation.md: new APK name, remove the unsigned APK block, state
the APK installs directly, add one line pointing testers at the Play
internal testing list.
docs/troubleshooting.md: rewrite the install failure entry to cover
downgrade attempts and the one time certificate mismatch for anyone who
sideloaded a self signed v0.1.0 to v0.3.0 APK (they must uninstall once).
README.md: new APK name, drop the sign it yourself caveat.
android/README.md: replace the manual signing section with the CI signing
story (env vars, conditional config, local builds stay unsigned, keep a
short self signing recipe), update the CI job table.
Done when: no doc references hawkeye--android-unsigned.apk except
as the documented fork fallback, and the next release notes mention the
uninstall once migration.
Done 2026-08-16: merged to main in Signed release APK and Google Play internal track upload #108 (with Android release signing, AAB build, and prerelease version codes #107). The release notes
reminder lands with the next release; releasing.md carries the instruction.
End to end validation.
Prerequisites: everything above. Confirm a green release-build run on main
showing the signed path. Push the next rc tag (v0.4.0-rc2 or later, never
v0.4.0-rc1 which was consumed by the manual upload). Confirm the GitHub
release contains hawkeye--android.apk and it installs directly on
a device, the Play internal track shows the new release with a higher
versionCode than the manual upload, and testers receive the update.
Done when: all of the above observed, then close this issue.
Done 2026-08-16: tag v0.4.0-rc2 ran the full pipeline green on the first
try. The GitHub release carries hawkeye-0.4.0-rc2-android.apk (signed), the
Play upload step succeeded and the internal track received versionCode
400002, and the released APK upgrade-installed over the sideloaded 400001
on a physical device, confirming signature continuity. https://github.com/PX4/Hawkeye/actions/runs/31965242813
Goal: every v* tag builds a signed Android App Bundle (AAB) and uploads it to
the Google Play internal test track under the Dronecode Foundation account.
The GitHub release APK becomes signed with the same upload key. The
application id stays com.px4.hawkeye.android and becomes permanent at the
first Play upload. The first upload must be done by hand because the Play
Developer API cannot create a new app.
Background for anyone picking up an item: releases are cut by pushing a v*
tag, which runs .github/workflows/release.yml. Its android-apk job builds
with
./gradlew assembleRelease -PhawkeyeVersionName=<version>(gradlew isin android/, not the repo root) and attaches
hawkeye--android-unsigned.apk to the GitHub release. versionCode is
derived in android/app/build.gradle.kts as major1000000 + minor1000 +
patch, which makes v0.4.0-rc1 collide with v0.4.0 (documented in
docs/developer/releasing.md). There is no signing config, keystore, or AAB
anywhere in the repo today. New Play apps must upload an AAB and are enrolled
in Play App Signing: Google holds the app signing key, we hold an upload key.
Highest shipped versionCode so far is 3000 (v0.3.0).
Console and secrets (manual, needs Dronecode Play account access)
Mint the upload keystore and back it up.
Run:
keytool -genkeypair -v -keystore hawkeye-upload.jks -storetype PKCS12 -alias hawkeye-upload -keyalg RSA -keysize 4096 -validity 10950 -dname "CN=Hawkeye, O=Dronecode Foundation"PKCS12 keystores use a single password for store and key. Record the
certificate fingerprint with
keytool -list -v -keystore hawkeye-upload.jks -alias hawkeye-upload | grep SHA256.Store the keystore file, password, alias, fingerprint, and creation date as
one entry in the Dronecode password manager. Note in the entry that Play App
Signing allows an upload key reset through the Play Console, so this backup
is a convenience rather than the last line of defense.
Done when: the entry exists in the password manager and the .jks is deleted
from any shared or synced folder.
Done 2026-08-16: keystore minted (RSA 4096, PKCS12, alias hawkeye-upload,
valid to 2056) with upload certificate SHA-256
DB:DF:32:2A:02:A8:2B:3D:8A:BA:E9:EE:26:1E:6C:EF:D9:C8:16:22:DD:0D:A3:03:B7:4F:A2:B1:A5:D4:89:FE.
Add the three signing secrets to the PX4/Hawkeye repo.
From a machine with the keystore:
gh secret set ANDROID_UPLOAD_KEYSTORE_BASE64 --repo PX4/Hawkeye --body "$(base64 -i hawkeye-upload.jks)"gh secret set ANDROID_UPLOAD_KEYSTORE_PASSWORD --repo PX4/Hawkeye(paste the password)gh secret set ANDROID_UPLOAD_KEY_ALIAS --repo PX4/Hawkeye --body hawkeye-uploadDone when: all three appear in the repo secrets list.
Done 2026-08-16: all three secrets set, and a workflow_dispatch run of
android.yml exercised the full signed path with them (keystore decode,
signed build, apksigner and jarsigner verification all passing):
https://github.com/PX4/Hawkeye/actions/runs/31952546318
Create the Hawkeye app in the Play Console.
In the Dronecode Foundation Play Console account: All apps, Create app.
Name Hawkeye, type App, Free, default language English. This step cannot be
done through the API.
Done when: the app exists in the console (its package name is fixed later
by the first AAB upload, which must be built with applicationId
com.px4.hawkeye.android).
Set up the internal testing track and tester list.
Play Console: Testing, Internal testing, create an email list (up to 100
testers) and attach it to the track. Internal testing does not require a
store listing, content rating, or data safety form to roll out; if the
console blocks on a specific declaration anyway, complete only what it
demands.
Done when: the internal track shows the tester list attached.
Done 2026-08-16: track is live and testers join through the opt-in link
https://play.google.com/apps/internaltest/4700645534848215513
Note for onboarding testers: a tester must be on the email list AND visit
the opt-in link once before the app shows up for them in Play.
Build and upload the first signed AAB by hand.
Prerequisite: the Gradle signing item under Repository changes is merged.
With the keystore locally:
export HAWKEYE_UPLOAD_KEYSTORE=/absolute/path/hawkeye-upload.jksexport HAWKEYE_UPLOAD_KEYSTORE_PASSWORD=<password>export HAWKEYE_UPLOAD_KEY_ALIAS=hawkeye-uploadcd android && ./gradlew bundleRelease -PhawkeyeVersionName=0.4.0-rc1Upload android/app/build/outputs/bundle/release/app-release.aab to the
internal testing track, accept the Google generated app signing key when
prompted (this enrolls Play App Signing), and roll out. This consumes
versionCode 400001, so the git tag v0.4.0-rc1 must never be pushed;
automation starts at v0.4.0-rc2.
Done when: the internal track shows release 0.4.0-rc1 rolled out and
testers can install it.
Done 2026-08-16: 0.4.0-rc1 (versionCode 400001) uploaded and rolled out to
internal testing; Play App Signing enrolled with a Google generated app
signing key. The git tag v0.4.0-rc1 is therefore permanently reserved and
must never be pushed; automation starts at v0.4.0-rc2.
Create the service account for CI uploads.
In Google Cloud Console, in a Dronecode owned project (create one named
hawkeye-play-publisher if none fits): enable the Google Play Android
Developer API, create a service account (no GCP roles needed), and create a
JSON key for it.
Done when: the JSON key file is downloaded and the API shows enabled.
Grant the service account access in the Play Console.
Play Console: Users and permissions, Invite new user, enter the service
account email. Grant app level access to Hawkeye only, with permissions
View app information (read only) and Release apps to testing tracks.
Nothing for production or app signing.
Known delay: a freshly invited service account can return API permission
errors for up to 24 hours; wait or edit any console setting before
debugging a failed upload.
Done when: the service account is listed under Users and permissions with
those two permissions.
Add the service account secret and clean up.
gh secret set PLAY_SERVICE_ACCOUNT_JSON --repo PX4/Hawkeye < service-account.jsonThen delete every local copy of the JSON key.
Done when: the secret exists and no local copy of the key remains.
Done 2026-08-16: hawkeye-ci-publisher@api-7299061481369082666-700664
.iam.gserviceaccount.com created in the Play linked GCP project with no GCP
roles, JSON key stored as PLAY_SERVICE_ACCOUNT_JSON and deleted locally,
and the account granted View app information plus Release apps to testing
tracks on Hawkeye only.
Repository changes
Ignore keystores. Add
*.jksand*.keystoreto the root.gitignore under the Android section.
Done when:
git check-ignore hawkeye-upload.jksmatches.Conditional upload signing in Gradle.
File: android/app/build.gradle.kts. Read env vars via
providers.environmentVariable: HAWKEYE_UPLOAD_KEYSTORE (path to a decoded
.jks), HAWKEYE_UPLOAD_KEYSTORE_PASSWORD, HAWKEYE_UPLOAD_KEY_ALIAS. When the
keystore var is present, create signingConfigs "upload" (keyPassword equals
storePassword, PKCS12) and set it on the release build type; when absent,
change nothing so local, PR, and fork builds keep producing an unsigned
release APK. Note: when signed, the output becomes app-release.apk instead
of app-release-unsigned.apk; the verify script resolves the file through
output-metadata.json so no path fixes are needed.
Done when: a local
./gradlew assembleRelease -PhawkeyeVersionName=0.0.0-devwithout env vars still builds unsigned, and with the three env vars set the
APK passes
apksigner verify.versionCode scheme with a prerelease component.
File: android/app/build.gradle.kts (the hawkeyeVersionCode block).
New formula: major100000000 + minor100000 + patch*100 + rc, where rc is
99 for a final version (no suffix), N for suffix rcN with N in 1..98, and 0
for suffixes dev and ci. Any other suffix fails the build. Require major in
0..20 so the maximum stays below the Play cap of 2100000000. Examples:
0.4.0-rc1 gives 400001, 0.4.0 gives 400099, 1.2.3 gives 100200399. Keep the
existing coerceAtLeast(1) so 0.0.0-dev still yields 1. All new codes exceed
3000 (shipped v0.3.0), so sideload upgrades stay monotonic. Update the
comment block above it.
Done when: the examples above hold (check with aapt dump badging on a built
APK) and an unknown suffix fails the build with a clear message.
Signed mode for the APK verify script.
File: android/scripts/verify-release-apk.sh. Add an optional --signed flag
preserving current behavior without it. With --signed, locate apksigner in
the newest $ANDROID_SDK_ROOT/build-tools/* (fall back to $ANDROID_HOME) and
fail the script if
apksigner verify --print-certsfails.Done when: the script passes on a signed APK with --signed, fails on an
unsigned APK with --signed, and is unchanged without the flag.
New AAB verify script.
New file: android/scripts/verify-release-bundle.sh, matching the style of
verify-release-apk.sh. Given the path to an .aab, assert it exists and that
unzip -llists base/lib/arm64-v8a/libhawkeye.so,base/lib/x86_64/libhawkeye.so, and the four asset trees base/assets/models,
base/assets/shaders, base/assets/fonts, base/assets/themes. No bundletool
dependency.
Done when: the script passes on a real bundleRelease output and fails when
pointed at an APK or a bundle missing an asset tree.
Exercise signing and bundling in android.yml before any tag.
File: .github/workflows/android.yml, job release-build. Route the keystore
secret through job env (UPLOAD_KEYSTORE_B64 from
secrets.ANDROID_UPLOAD_KEYSTORE_BASE64) because the secrets context is
unavailable in job level if. Add a step guarded by
if: env.UPLOAD_KEYSTORE_B64 != ''that decodes it to$RUNNER_TEMP/upload-keystore.jks and exports HAWKEYE_UPLOAD_KEYSTORE via
GITHUB_ENV. Build with
./gradlew assembleRelease bundleRelease -PhawkeyeVersionName=0.0.0-ci(passwords and alias as step env fromsecrets). Verify the APK with --signed when the keystore is present and
without it otherwise, and verify the AAB with the new script. Never upload
to Play from this workflow. Forks without secrets must stay green on the
unsigned path.
Done when: a run on main with secrets shows the signed verify and AAB
verify steps passing, and a PR from a fork still passes.
Done 2026-08-16: the post-merge run on main decoded the keystore and passed
the signed APK and AAB verifications:
https://github.com/PX4/Hawkeye/actions/runs/31954206015
Sign, bundle, and upload in release.yml.
File: .github/workflows/release.yml, job android-apk (rename to android,
update its name accordingly). Same keystore decode pattern as android.yml.
Build
assembleRelease bundleReleasewith -PhawkeyeVersionName fromneeds.source-tarball.outputs.version. Verify the APK (--signed when the
keystore is present) and the AAB. Stage the APK as
hawkeye--android.apk when signed, or keep
hawkeye--android-unsigned.apk when not (forks), and upload it to
the GitHub release with gh release upload as today. Remove the stale
comment about dropping the -unsigned suffix. Add a final step, guarded on
env.PLAY_SA_JSON != ''(routed from secrets.PLAY_SERVICE_ACCOUNT_JSON viajob env), using the r0adkll/upload-google-play action pinned to a commit
SHA with a version comment: serviceAccountJsonPlainText from the secret,
packageName com.px4.hawkeye.android, releaseFiles pointing at
android/app/build/outputs/bundle/release/app-release.aab, track internal,
status completed. Do not gate on prerelease tags: rc uploads are the point
of the internal track. Do not attach the AAB to the GitHub release.
Done when: workflow lint passes and every new step is guarded so a fork or
a repo without Play secrets falls back to today's behavior.
Docs.
docs/developer/releasing.md: asset name hawkeye--android.apk, new
versionCode formula with an example table (v0.4.0-rc1 gives 400001, v0.4.0
gives 400099, v1.2.3 gives 100200399, no tag gives 1), delete the stale
prerelease collision warning, rewrite the Android APK section (signed with
the upload key, secret names, fork fallback), add a Google Play internal
track section covering what uploads when and the service account setup.
docs/installation.md: new APK name, remove the unsigned APK block, state
the APK installs directly, add one line pointing testers at the Play
internal testing list.
docs/troubleshooting.md: rewrite the install failure entry to cover
downgrade attempts and the one time certificate mismatch for anyone who
sideloaded a self signed v0.1.0 to v0.3.0 APK (they must uninstall once).
README.md: new APK name, drop the sign it yourself caveat.
android/README.md: replace the manual signing section with the CI signing
story (env vars, conditional config, local builds stay unsigned, keep a
short self signing recipe), update the CI job table.
Done when: no doc references hawkeye--android-unsigned.apk except
as the documented fork fallback, and the next release notes mention the
uninstall once migration.
Done 2026-08-16: merged to main in Signed release APK and Google Play internal track upload #108 (with Android release signing, AAB build, and prerelease version codes #107). The release notes
reminder lands with the next release; releasing.md carries the instruction.
End to end validation.
Prerequisites: everything above. Confirm a green release-build run on main
showing the signed path. Push the next rc tag (v0.4.0-rc2 or later, never
v0.4.0-rc1 which was consumed by the manual upload). Confirm the GitHub
release contains hawkeye--android.apk and it installs directly on
a device, the Play internal track shows the new release with a higher
versionCode than the manual upload, and testers receive the update.
Done when: all of the above observed, then close this issue.
Done 2026-08-16: tag v0.4.0-rc2 ran the full pipeline green on the first
try. The GitHub release carries hawkeye-0.4.0-rc2-android.apk (signed), the
Play upload step succeeded and the internal track received versionCode
400002, and the released APK upgrade-installed over the sideloaded 400001
on a physical device, confirming signature continuity.
https://github.com/PX4/Hawkeye/actions/runs/31965242813