From 67cc851b15bf857688c88787e275f6a6b6c21a95 Mon Sep 17 00:00:00 2001 From: b-dev-mobile Date: Tue, 7 Jul 2026 13:17:20 +0000 Subject: [PATCH] =?UTF-8?q?=D0=AD=D1=82=D0=B0=D0=BF=200=20=D0=BF.1:=20rele?= =?UTF-8?q?ase=20=D1=81=D0=BE=D0=B1=D0=B8=D1=80=D0=B0=D0=B5=D0=BC=20=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=20(signingC?= =?UTF-8?q?onfig=20=D0=B8=D0=B7=20env=20+=20R8-=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - signingConfig.release читает F7_KEYSTORE_FILE/PASSWORD, F7_KEY_ALIAS/PASSWORD из env или -P; фолбэк на debug-подпись если не заданы. Схемы v2+v3. - R8: app/proguard-rules.pro (kotlinx.serialization, Firebase, JS-мосты, Coil); vendor consumer-rules.pro через consumerProguardFiles (WebRTC/LoganSquare/Dagger/ RxJava/Parceler/EventBus + dontwarn geogson/joda из missing_rules.txt). - Проверено на инфре B (JDK 21 + Android SDK 36): assembleRelease BUILD SUCCESSFUL, подписанный APK 99 МБ (было ~130 debug), apksigner verify v2+v3 OK. - Доки: docs/RELEASE-SIGNING.md, docs/AUDIT-2026-07-07.md. --- CHANGELOG.md | 1 + app/build.gradle | 24 +++++++ app/proguard-rules.pro | 52 +++++++++++++- docs/RELEASE-SIGNING.md | 63 ++++++++++++++++ vendor/talk-android/app/build.gradle | 5 ++ vendor/talk-android/app/consumer-rules.pro | 83 ++++++++++++++++++++++ 6 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 docs/RELEASE-SIGNING.md create mode 100644 vendor/talk-android/app/consumer-rules.pro diff --git a/CHANGELOG.md b/CHANGELOG.md index 3966baf..63d2be0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Формат: `ГГГГ-ММ-ДД | версия | изменение | контракты | риск` +- 2026-07-07 | v0.5.114 (122) | Аудит + Этап 0 п.1 (release собираем и подписан): добавлен signingConfig (ключ из env F7_KEYSTORE_*/-P, фолбэк на debug); написаны R8-правила — app/proguard-rules.pro (kotlinx.serialization/Firebase/JS-мосты) + vendor consumer-rules.pro через consumerProguardFiles (WebRTC/LoganSquare/Dagger/RxJava/Parceler/EventBus + dontwarn geogson/joda); подпись v2+v3. Проверено локально (JDK 21 + Android SDK 36 поставлены на инфру B): assembleRelease → BUILD SUCCESSFUL, подписанный APK 99 МБ (было ~130 debug). Полный аудит — docs/AUDIT-2026-07-07.md; подпись — docs/RELEASE-SIGNING.md | контракты не менялись; сборочный контур зафиксирован | низкий: код приложения не тронут, изменения только в сборке; APK всё ещё 4 ABI (ABI-split — п.2) - 2026-07-07 | v0.5.114 (122) | Санитария по mail/035: дефолт-URL входа → BuildConfig.DEFAULT_SERVER_URL (свойство f7DefaultServerUrl, по умолчанию пусто); убран preconnect к боевому hpb-prod.f7cloud.ru (TalkHelper); нейтральный пример URL в тексте ошибки | INTEGRATION.md: зашитых касаний хостов A больше нет | низкий: preconnect — только оптимизация; сборка на билд-сервере ещё не прогонялась - 2026-07-07 | v0.5.113 (121) | Онбординг в федерацию F7: заведены INTEGRATION.md и CHANGELOG.md, роль `b-dev-mobile` (ячейка B). Код без изменений | INTEGRATION.md создан: задекларированы потребляемые контракты тенанта (OCS/WebDAV/CalDAV/CardDAV/Mail/spreed v4/Dashboard/Richdocuments/f7push) | нет - до 2026-07-07 | ≤0.5.113 | Разработка вне федерации (сессии Cursor Agent): нативные модули файлы/почта/календарь/контакты/задачи/Talk/поддержка/Deck | хронология — PROJECT-STATUS.md | — diff --git a/app/build.gradle b/app/build.gradle index a8e42f2..0e447d5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,10 +9,32 @@ if (googleServicesFile.exists()) { apply plugin: 'com.google.gms.google-services' } +// Release-подпись читается из окружения/gradle-properties (боевой keystore вне git, +// в CI — из secrets). Переменные: F7_KEYSTORE_FILE, F7_KEYSTORE_PASSWORD, +// F7_KEY_ALIAS, F7_KEY_PASSWORD (можно и как -P… gradle-property). Если не заданы — +// release подписывается debug-ключом (локальная сборка/QA работает без keystore). +def f7KeystoreFile = System.getenv('F7_KEYSTORE_FILE') ?: project.findProperty('F7_KEYSTORE_FILE') +def f7HasReleaseSigning = f7KeystoreFile != null && file(f7KeystoreFile).exists() + android { namespace 'ru.forbion.f7cloud.mobile' compileSdk 36 + signingConfigs { + if (f7HasReleaseSigning) { + release { + storeFile file(f7KeystoreFile) + storePassword System.getenv('F7_KEYSTORE_PASSWORD') ?: project.findProperty('F7_KEYSTORE_PASSWORD') + keyAlias System.getenv('F7_KEY_ALIAS') ?: project.findProperty('F7_KEY_ALIAS') + keyPassword System.getenv('F7_KEY_PASSWORD') ?: project.findProperty('F7_KEY_PASSWORD') + // v2 обязателен (minSdk 26); v3 — под будущую ротацию ключа. v1 не нужен (minSdk>=24). + enableV1Signing false + enableV2Signing true + enableV3Signing true + } + } + } + defaultConfig { applicationId 'ru.forbion.f7cloud.mobile' minSdk 26 @@ -35,6 +57,8 @@ android { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + // боевой keystore — если задан env/-P; иначе debug-ключ (не для распространения) + signingConfig f7HasReleaseSigning ? signingConfigs.release : signingConfigs.debug } } diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index a5889bd..6115599 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1 +1,51 @@ -# Project specific ProGuard rules. +# ProGuard/R8 rules приложения :app (F7cloud Mobile). +# Vendor-зависимости (WebRTC/LoganSquare/Dagger/RxJava/Parceler/EventBus) держат +# vendor/talk-android/app/consumer-rules.pro (доезжает сюда через consumerProguardFiles). +# Здесь — правила для КОДА ПРИЛОЖЕНИЯ и его прямых библиотек. + +# --- Общие атрибуты (нужны рефлексии/сериализации/отладке стектрейсов) --- +-keepattributes Signature,*Annotation*,EnclosingMethod,InnerClasses +-keepattributes SourceFile,LineNumberTable +-renamesourcefileattribute SourceFile + +# --- kotlinx.serialization (@Serializable в feature-модулях) --- +# Держим сгенерированные сериализаторы; без этого @Serializable падает в рантайме. +-keepclassmembers @kotlinx.serialization.Serializable class ** { + *** Companion; + *** INSTANCE; + kotlinx.serialization.KSerializer serializer(...); +} +-keepclasseswithmembers class ** { + @kotlinx.serialization.Serializable ; +} +-keep,includedescriptorclasses class ru.forbion.f7cloud.**$$serializer { *; } +-dontwarn kotlinx.serialization.** + +# --- Kotlin метаданные/корутины --- +-keep class kotlin.Metadata { *; } +-dontwarn kotlinx.coroutines.** + +# --- enum'ы приложения (values()/valueOf по имени) --- +-keepclassmembers enum ru.forbion.f7cloud.** { *; } + +# --- Coil (декодеры подгружаются по имени) --- +-dontwarn coil.** +-keep class coil.decode.** { *; } + +# --- OkHttp/Okio/security-провайдеры (косметические ворнинги R8) --- +-dontwarn okhttp3.internal.platform.** +-dontwarn org.conscrypt.** +-dontwarn org.bouncycastle.** +-dontwarn org.openjsse.** + +# --- Firebase Cloud Messaging --- +-keep class com.google.firebase.** { *; } +-dontwarn com.google.firebase.** + +# --- WebView JS-мосты (@JavascriptInterface вызывается из JS по имени) --- +-keepclassmembers class * { + @android.webkit.JavascriptInterface ; +} + +# --- Compose обычно не требует правил; косметика --- +-dontwarn org.jetbrains.annotations.** diff --git a/docs/RELEASE-SIGNING.md b/docs/RELEASE-SIGNING.md new file mode 100644 index 0000000..bc22454 --- /dev/null +++ b/docs/RELEASE-SIGNING.md @@ -0,0 +1,63 @@ +# Release-подпись и минификация f7cloud_mobile + +## TL;DR +`assembleRelease` теперь собирает **подписанный, минифицированный** APK. Ключ подписи +берётся из окружения; при его отсутствии release подписывается debug-ключом (для локального QA). + +## Переменные окружения (боевой keystore — вне git, в CI — из secrets) + +| Переменная | Назначение | +|---|---| +| `F7_KEYSTORE_FILE` | путь к keystore (`.jks`/`.p12`) | +| `F7_KEYSTORE_PASSWORD` | пароль хранилища | +| `F7_KEY_ALIAS` | алиас ключа | +| `F7_KEY_PASSWORD` | пароль ключа | + +Можно задавать и как gradle-property (`-PF7_KEYSTORE_FILE=…`). Логика — в `app/build.gradle` +(`signingConfigs.release`): если `F7_KEYSTORE_FILE` задан и файл существует → подпись боевым +ключом (схемы v2+v3, v1 отключён — minSdk 26); иначе → debug-ключ (APK **не для распространения**). + +## Генерация боевого keystore (один раз, хранить в секрете и с бэкапом!) + +```bash +keytool -genkeypair -v \ + -keystore f7cloud-release.p12 -storetype PKCS12 \ + -alias f7cloud -keyalg RSA -keysize 4096 -validity 10000 \ + -dname "CN=F7cloud Mobile, O=F7cloud, C=RU" +``` + +⚠️ **PKCS12 (дефолт современного keytool): пароль ключа ОБЯЗАН совпадать с паролем хранилища.** +Отдельный `-keypass` игнорируется → при сборке `KeytoolException: Given final block not properly +padded`. Поэтому `F7_KEY_PASSWORD == F7_KEYSTORE_PASSWORD`. + +⚠️ Боевой ключ подписи **неизменен на весь срок жизни приложения**: потеря = невозможность +выката обновлений существующим пользователям. Хранить в секрете + офлайн-бэкап. + +## Локальная сборка (пример) + +```bash +export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 +export ANDROID_HOME=/opt/android-sdk +source /path/to/signing.env # экспорт четырёх F7_* переменных +./gradlew :app:assembleRelease +# APK: app/build/outputs/apk/release/f7cloud-mobile-v--release.apk +# проверка: $ANDROID_HOME/build-tools/36.0.0/apksigner verify --verbose +``` + +## Минификация (R8) + +Release: `minifyEnabled true` + `shrinkResources true`. Keep-правила: +- **app-уровень** — `app/proguard-rules.pro` (kotlinx.serialization, Firebase, JS-мосты WebView, Coil). +- **vendor-зависимости** — `vendor/talk-android/app/consumer-rules.pro`, подключён через + `consumerProguardFiles` (WebRTC JNI, LoganSquare `$$JsonObjectMapper`, Dagger, RxJava, Parceler, + EventBus, conscrypt + `-dontwarn` geogson/joda). Vendor — library без своей минификации, поэтому + его правила доезжают до R8 приложения ТОЛЬКО через consumer-файл. + +При добавлении рефлексивных библиотек: собрать release, при `Missing class` R8 сгенерирует точные +правила в `app/build/intermediates/mapping/release/minifyReleaseWithR8/missing_rules.txt` — перенести +их в соответствующий proguard-файл. + +## Размер APK + +R8 + shrinkResources: **~130 МБ (debug) → 99 МБ (release, все 4 ABI)**. Дальнейшее сокращение +до ~30–45 МБ — ABI-split/`resConfigs` (пункт 2 этапа 0 плана аудита), пока не сделано. diff --git a/vendor/talk-android/app/build.gradle b/vendor/talk-android/app/build.gradle index d2142a0..e23efcb 100644 --- a/vendor/talk-android/app/build.gradle +++ b/vendor/talk-android/app/build.gradle @@ -34,6 +34,11 @@ android { targetSdkVersion 36 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + // F7cloud: keep-правила зависимостей vendor (WebRTC/LoganSquare/Dagger/RxJava/ + // Parceler/EventBus) для R8 приложения-потребителя (:app). Vendor — library без + // минификации, поэтому только через consumerProguardFiles правила доедут до :app. + consumerProguardFiles 'consumer-rules.pro' + // mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable) // xx .xxx .xx .xx versionCode 230000090 diff --git a/vendor/talk-android/app/consumer-rules.pro b/vendor/talk-android/app/consumer-rules.pro new file mode 100644 index 0000000..8b1605d --- /dev/null +++ b/vendor/talk-android/app/consumer-rules.pro @@ -0,0 +1,83 @@ +# Consumer ProGuard/R8 rules для :vendor:talk-app (F7cloud). +# Эти правила ПРОПАГИРУЮТСЯ в R8 приложения :app (vendor собирается как library +# с minifyEnabled false, поэтому обычные proguardFiles на потребителя не влияют — +# нужен именно consumerProguardFiles, см. app/build.gradle). +# Защищают зависимости vendor-форка Nextcloud Talk от переименования/удаления R8. + +# --- WebRTC (JNI: нативный libjingle_peerconnection_so.so зовёт org.webrtc.* по имени) --- +-keep class org.webrtc.** { *; } +-dontwarn org.webrtc.** + +# --- LoganSquare (converter Talk API: сгенерированные $$JsonObjectMapper через рефлексию) --- +-keep class com.bluelinelabs.logansquare.** { *; } +-keep @com.bluelinelabs.logansquare.annotation.JsonObject class * { *; } +-keep class **$$JsonObjectMapper { *; } +-keepnames class * extends com.bluelinelabs.logansquare.JsonMapper +-keepclassmembers class * { + @com.bluelinelabs.logansquare.annotation.JsonField *; +} + +# --- Jackson core (используется LoganSquare) --- +-dontwarn com.fasterxml.jackson.** +-keep class com.fasterxml.jackson.** { *; } + +# --- Модели Talk API (парсятся LoganSquare/Gson по именам полей) --- +-keep class ru.f7cloud.talk.models.** { *; } + +# --- Gson (converter-gson + модели) --- +-keepattributes Signature,*Annotation*,EnclosingMethod,InnerClasses +-keep class com.google.gson.** { *; } +-dontwarn com.google.gson.** +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +# --- Retrofit 2 --- +-keep,allowobfuscation,allowshrinking interface retrofit2.Call +-keep,allowobfuscation,allowshrinking class retrofit2.Response +-keepclasseswithmembers class * { @retrofit2.http.* ; } +-dontwarn retrofit2.** +-dontwarn okio.** +-keepattributes RuntimeVisibleAnnotations,RuntimeVisibleParameterAnnotations + +# --- Parceler (сгенерированные $$Parcelable) --- +-keep class **$$Parcelable { *; } +-keep interface org.parceler.Parcel +-keep @org.parceler.Parcel class * { *; } +-keepclassmembers class **$$Parcelable { + public static final ** CREATOR; +} + +# --- EventBus (@Subscribe вызывается рефлексией) --- +-keepattributes *Annotation* +-keepclassmembers class * { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } +-keep class org.greenrobot.eventbus.android.** { *; } + +# --- Dagger / autodagger2 --- +-dontwarn dagger.** +-dontwarn com.github.lukaspili.autodagger2.** + +# --- RxJava2 (опциональные зависимости на этапе компиляции) --- +-dontwarn java.util.concurrent.Flow* +-dontwarn sun.misc.** +-dontwarn org.reactivestreams.** + +# --- Conscrypt (опциональный security-provider) --- +-dontwarn org.conscrypt.** +-keep class org.conscrypt.** { *; } + +# --- прочие опциональные/аннотационные зависимости, на которые ругается R8 --- +-dontwarn javax.annotation.** +-dontwarn org.bouncycastle.** +-dontwarn org.slf4j.** +-dontwarn okhttp3.internal.platform.** + +# --- недостающие опциональные refs (из missing_rules.txt R8): geogson→старая guava, +# joda-convert аннотации joda-time (maps-стек Talk). Классы отсутствуют в рантайме, +# код их не вызывает — безопасно подавить. --- +-dontwarn com.google.common.base.Objects$ToStringHelper +-dontwarn org.joda.convert.FromString +-dontwarn org.joda.convert.ToString