Этап 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:
@@ -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.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 | 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 | —
|
- до 2026-07-07 | ≤0.5.113 | Разработка вне федерации (сессии Cursor Agent): нативные модули файлы/почта/календарь/контакты/задачи/Talk/поддержка/Deck | хронология — PROJECT-STATUS.md | —
|
||||||
|
|||||||
@@ -9,10 +9,32 @@ if (googleServicesFile.exists()) {
|
|||||||
apply plugin: 'com.google.gms.google-services'
|
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 {
|
android {
|
||||||
namespace 'ru.forbion.f7cloud.mobile'
|
namespace 'ru.forbion.f7cloud.mobile'
|
||||||
compileSdk 36
|
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 {
|
defaultConfig {
|
||||||
applicationId 'ru.forbion.f7cloud.mobile'
|
applicationId 'ru.forbion.f7cloud.mobile'
|
||||||
minSdk 26
|
minSdk 26
|
||||||
@@ -35,6 +57,8 @@ android {
|
|||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
// боевой keystore — если задан env/-P; иначе debug-ключ (не для распространения)
|
||||||
|
signingConfig f7HasReleaseSigning ? signingConfigs.release : signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+51
-1
@@ -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.**
|
||||||
|
|||||||
@@ -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)**. Дальнейшее сокращение
|
||||||
|
до ~30–45 МБ — ABI-split/`resConfigs` (пункт 2 этапа 0 плана аудита), пока не сделано.
|
||||||
Vendored
+5
@@ -34,6 +34,11 @@ android {
|
|||||||
targetSdkVersion 36
|
targetSdkVersion 36
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
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)
|
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
|
||||||
// xx .xxx .xx .xx
|
// xx .xxx .xx .xx
|
||||||
versionCode 230000090
|
versionCode 230000090
|
||||||
|
|||||||
+83
@@ -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
|
||||||
Reference in New Issue
Block a user