Этап 0 п.1: release собираем и подписан (signingConfig из env + R8-правила)

- 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.
This commit is contained in:
b-dev-mobile
2026-07-07 13:17:20 +00:00
parent 1641ad8ab8
commit 67cc851b15
6 changed files with 227 additions and 1 deletions
+1
View File
@@ -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 | —
+24
View File
@@ -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
}
}
+51 -1
View File
@@ -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 <fields>;
}
-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 <methods>;
}
# --- Compose обычно не требует правил; косметика ---
-dontwarn org.jetbrains.annotations.**
+63
View File
@@ -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<ver>-<code>-release.apk
# проверка: $ANDROID_HOME/build-tools/36.0.0/apksigner verify --verbose <apk>
```
## Минификация (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)**. Дальнейшее сокращение
до ~3045 МБ — ABI-split/`resConfigs` (пункт 2 этапа 0 плана аудита), пока не сделано.
+5
View File
@@ -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
+83
View File
@@ -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 <fields>;
}
# --- Retrofit 2 ---
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
-keep,allowobfuscation,allowshrinking class retrofit2.Response
-keepclasseswithmembers class * { @retrofit2.http.* <methods>; }
-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 <methods>;
}
-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