Document Android debug-signed rollout policy

This commit is contained in:
Курнат Андрей
2026-06-10 06:52:29 +03:00
parent 0961491d52
commit 4f69012249
5 changed files with 40 additions and 2 deletions
+23
View File
@@ -0,0 +1,23 @@
# QSfera Android Argus Rollout Policy
## Sources
- Android update requirement: Android Developers says an update must use the same application ID and the same signing certificate as the installed app, unless it contains a valid proof-of-rotation: <https://developer.android.com/google/play/app-updates>.
- QSfera update implementation: `Android/qsferaApp/src/main/java/eu/qsfera/android/presentation/settings/update/AppUpdateManager.kt`.
- QSfera Android version and release signing env names: `Android/qsferaApp/build.gradle`.
- Current Argus release and certificate facts verified for this production-readiness pass: `PRODUCTION_READINESS.md`, section `Android Argus Publication Check`.
## Policy
- Stable Argus releases for `qsfera-mobile` must keep the same package name and production signing certificate after the production-signed `1.3.9` / `37` release.
- The in-app updater must not bypass certificate mismatch. `AppUpdateManager` validates package name, monotonically newer `versionCode`, Argus `androidVersionCode`, and signer digest before starting Android package installation.
- A client installed from the debug-signed `1.3.5` / `33` Argus APK cannot be moved to the production-signed `1.3.9` / `37` APK by the in-app updater because the signer digest check intentionally fails.
- For debug-signed `1.3.5` installs, the supported migration path is: back up unsynced local files, uninstall the debug-signed build, then install the current stable QSfera APK from Argus.
- On certificate mismatch, the Android app must show an explicit reinstall-required dialog instead of a generic update error.
## Release Checklist
1. Build the stable APK with the same release key material through `QSFERA_RELEASE_KEYSTORE`, `QSFERA_RELEASE_KEYSTORE_PASSWORD`, `QSFERA_RELEASE_KEY_ALIAS`, and `QSFERA_RELEASE_KEY_PASSWORD`.
2. Verify the APK certificate before upload with `apksigner verify --print-certs`.
3. Publish to Argus only after certificate, package name, version code, and SHA-256 checks pass.
4. Verify the published manifest and download with `scripts/verify-production-state.ps1 -VerifyAndroidDownload` from the repository root.
@@ -123,6 +123,15 @@ class AppUpdateManager(private val fragment: Fragment) {
}
private fun showErrorDialog(context: Context, error: Throwable) {
if (error is SignatureMismatchException) {
AlertDialog.Builder(context)
.setTitle(R.string.update_reinstall_required_title)
.setMessage(R.string.update_reinstall_required_message)
.setPositiveButton(android.R.string.ok, null)
.show()
return
}
AlertDialog.Builder(context)
.setTitle(R.string.common_error)
.setMessage(context.getString(R.string.update_error, error.message ?: error.javaClass.simpleName))
@@ -216,7 +225,7 @@ class AppUpdateManager(private val fragment: Fragment) {
}
if (!hasTrustedSignature(context, downloadedPackageInfo)) {
error(context.getString(R.string.update_signature_error))
throw SignatureMismatchException(context.getString(R.string.update_signature_error))
}
}
@@ -324,6 +333,8 @@ class AppUpdateManager(private val fragment: Fragment) {
val sha256: String
)
private class SignatureMismatchException(message: String) : IllegalStateException(message)
companion object {
private const val ARGUS_BASE_URL = "https://argus.kusoft.xyz"
private const val ARGUS_MANIFEST_URL =
@@ -123,6 +123,8 @@
<string name="update_version_error">Скачанное обновление не новее установленного приложения.</string>
<string name="update_metadata_error">Скачанное обновление не совпадает с метаданными релиза в Аргус.</string>
<string name="update_signature_error">Скачанное обновление подписано не сертификатом установленного приложения.</string>
<string name="update_reinstall_required_title">Требуется переустановка</string>
<string name="update_reinstall_required_message">Эта установленная сборка подписана другим сертификатом, чем текущий релиз QSfera в Аргус. Android не может обновить её поверх установленной. Сохраните несинхронизированные файлы, удалите эту сборку и установите QSfera из Аргус.</string>
<string name="update_apk_parse_error">Скачанное обновление не удалось прочитать как Android-пакет.</string>
<string name="update_release_not_found">Последний Android-релиз не найден.</string>
<string name="pattern_enter_pattern">Пожалуйста, введите графический ключ</string>
@@ -125,6 +125,8 @@
<string name="update_version_error">Downloaded update is not newer than the installed app.</string>
<string name="update_metadata_error">Downloaded update does not match Argus release metadata.</string>
<string name="update_signature_error">Downloaded update is not signed with the installed app certificate.</string>
<string name="update_reinstall_required_title">Reinstall required</string>
<string name="update_reinstall_required_message">This installed build was signed with a different certificate than the current QSfera release in Argus. Android cannot update it in place. Back up unsynced files, uninstall this build, then install QSfera from Argus.</string>
<string name="update_apk_parse_error">Downloaded update could not be read as an Android package.</string>
<string name="update_release_not_found">Latest Android release was not found.</string>
<string name="pattern_enter_pattern">Please enter your pattern</string>
+1 -1
View File
@@ -70,12 +70,12 @@ Date: 2026-06-08.
- The previously published `1.3.5`/`33` APK matched Argus SHA-256 `b7d5bdcb49527c00983a15849252fa42189dee4287754803c46cf2f32e2130f3`, but `apksigner` reported `CN=Android Debug` with certificate SHA-256 `b86d745cecb01ca24bda03092fe94ca7801678513b97edd1175bcabfdea4e941`.
- The new `1.3.9`/`37` APK is signed by `CN=QSfera Argus Upload, O=QSfera, C=RU` with certificate SHA-256 `ea5c304c78ee333f6586f54c933d986c1f865da9c4e3595f8fe0df274c1ab877`.
- `AppUpdateManager` validates downloaded APK package name, greater `versionCode`, Argus `androidVersionCode`, and signer digest against the installed app signature. Because of that local code path, devices that installed debug-signed `1.3.5` should be treated as needing reinstall or an explicit migration plan before they can move to the production-signed `1.3.9`.
- The rollout policy for debug-signed `1.3.5` devices is documented in `Android/docs/argus-rollout-policy.md`: the app does not bypass signer mismatch; users must back up unsynced local files, uninstall the debug-signed build, then install the current stable APK from Argus. The Android app now shows a reinstall-required dialog for this certificate-mismatch path.
## Remaining P0
- Back up the gitignored Android release signing files created under `Android/signing/` and `Android/argus-signing.local.properties` in a secure external secret store. Without this key material, future in-place Android updates from the production-signed `1.3.9` cannot be signed with the same certificate.
- Provide CI/Argus signing secrets derived from the same release key: `QSFERA_RELEASE_KEYSTORE`, `QSFERA_RELEASE_KEYSTORE_PASSWORD`, `QSFERA_RELEASE_KEY_ALIAS`, and `QSFERA_RELEASE_KEY_PASSWORD`.
- Decide the rollout policy for any devices that installed the debug-signed Argus `1.3.5` APK. The production-signed `1.3.9` APK is the correct current release, but installed debug-signed clients will not satisfy the app's own signer-digest check for an in-place update.
- Fill production domains, ACME email, admin passwords, Keycloak passwords, and storage secrets from a secret manager. Do not commit real values.
- Run `./validate-production-env.sh .env` and then `docker compose config` for `Server/devtools/deployments/qsfera_full` with a filled production `.env`; Docker CLI is not available in this local environment, so compose rendering is not locally verified.
- Provide real Windows signing secrets in CI: `QSFERA_DESKTOP_SIGN_PACKAGE=True` and `QSFERA_DESKTOP_CODESIGN_CERTIFICATE` with the Craft-compatible signing certificate reference. The current local MSI is intentionally not published as a production update because `Get-AuthenticodeSignature` reports `NotSigned` and `Desktop/scripts/validate-production-release.ps1 -RequireSigning` fails until signing is configured.