Merge pull request #19 from Naveax/pr/add-android-supply-chain-ci
build: add reproducible Android release verification Resolved a conflict in android/app/build.gradle.kts. #19 was written against the pre-#10 file and re-added the pub-cache patch tasks that #10 deleted; only `sha256Hex()` was kept from that block, since `verifyReleaseAppConfig` needs it. Also restored `import java.io.File`, which #10 removed along with the patch tasks. #19's `sha256Hex(file: File)` names the type in its signature, and Kotlin does not auto-import `java.io.File`, so the release build would have failed at Gradle configuration without it. Verified after resolution: no orphaned references to `projectRootDir`, `localProperties`, `flutterSdkPath` or `patchFlutterVless`; both tests in test/release_verification_regression_test.dart pass; `flutter analyze` reports no errors or warnings. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_019HT2x2JvJNg7XNsJubfHFf
This commit was merged in pull request #19.
This commit is contained in:
commit
061bd41920
10 files changed
+9499
-17
No files matched your search
@@ -1,11 +1,18 @@
|
||||
import java.util.Properties
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
fun sha256Hex(file: File): String {
|
||||
val digest = MessageDigest.getInstance("SHA-256").digest(file.readBytes())
|
||||
return digest.joinToString("") { "%02x".format(it.toInt() and 0xff) }
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.paladinvpn.app"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
@@ -17,10 +24,6 @@ android {
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.add("META-INF/**")
|
||||
}
|
||||
|
||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||
val keystoreProperties = Properties()
|
||||
if (keystorePropertiesFile.exists()) {
|
||||
@@ -33,6 +36,68 @@ android {
|
||||
admobProperties.load(FileInputStream(admobPropertiesFile))
|
||||
}
|
||||
|
||||
// Explicit opt-in used only by GitHub Actions to exercise the real
|
||||
// release/R8 graph. Production release signing and config pinning remain
|
||||
// fail-closed outside that CI smoke path.
|
||||
val ciReleaseSmoke =
|
||||
System.getenv("GITHUB_ACTIONS") == "true" &&
|
||||
System.getenv("REVOLT_CI_RELEASE_SMOKE") == "true"
|
||||
|
||||
val appConfigFile = rootProject.file("../lib/logic/app_config.dart")
|
||||
val verifyReleaseAppConfig = tasks.register("verifyReleaseAppConfig") {
|
||||
group = "verification"
|
||||
description = "Verify the production Dart app config against REVOLT_APP_CONFIG_SHA256."
|
||||
doLast {
|
||||
if (ciReleaseSmoke) {
|
||||
logger.lifecycle("Skipping production app-config pin for CI release smoke build")
|
||||
return@doLast
|
||||
}
|
||||
|
||||
val expected = System.getenv("REVOLT_APP_CONFIG_SHA256")?.trim()?.lowercase()
|
||||
if (expected.isNullOrEmpty()) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"REVOLT_APP_CONFIG_SHA256 is required for production release builds"
|
||||
)
|
||||
}
|
||||
if (!Regex("[0-9a-f]{64}").matches(expected)) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"REVOLT_APP_CONFIG_SHA256 must be exactly 64 lowercase hexadecimal characters"
|
||||
)
|
||||
}
|
||||
if (!appConfigFile.isFile) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"Production app config is missing: ${appConfigFile.absolutePath}"
|
||||
)
|
||||
}
|
||||
|
||||
val configText = appConfigFile.readText()
|
||||
val templateMarkers = listOf(
|
||||
"'0.0.0.0'",
|
||||
"YOUR_DOMAIN",
|
||||
"YOUR_GITHUB_USERNAME",
|
||||
"YOUR_REPO_NAME",
|
||||
"ca-app-pub-0000000000000000/0000000000",
|
||||
)
|
||||
val remainingMarker = templateMarkers.firstOrNull(configText::contains)
|
||||
if (remainingMarker != null) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"Production app config still contains template marker: $remainingMarker"
|
||||
)
|
||||
}
|
||||
|
||||
val actual = sha256Hex(appConfigFile)
|
||||
if (actual != expected) {
|
||||
throw org.gradle.api.GradleException(
|
||||
"Production app config SHA-256 mismatch: expected $expected, got $actual"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.matching { it.name == "preReleaseBuild" }.configureEach {
|
||||
dependsOn(verifyReleaseAppConfig)
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.paladinvpn.app"
|
||||
minSdk = flutter.minSdkVersion
|
||||
@@ -53,7 +118,11 @@ android {
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
signingConfig = if (ciReleaseSmoke) {
|
||||
signingConfigs.getByName("debug")
|
||||
} else {
|
||||
signingConfigs.getByName("release")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
|
||||
Vendored
+2
-3
@@ -1,4 +1,4 @@
|
||||
# PaladinVPN ProGuard / R8 Rules
|
||||
# ReVoltVPN ProGuard / R8 Rules
|
||||
# ==============================================================================
|
||||
# These rules prevent R8 from stripping classes and methods that are accessed
|
||||
# via JNI (Java Native Interface) or reflection, which would cause
|
||||
@@ -11,9 +11,8 @@
|
||||
-keep class xray.** { *; }
|
||||
|
||||
# ── Flutter / Platform Channels ───────────────────────────────────────────────
|
||||
# io.flutter.** already includes embedding and plugin subpackages.
|
||||
-keep class io.flutter.** { *; }
|
||||
-keep class io.flutter.embedding.** { *; }
|
||||
-keep class io.flutter.plugin.** { *; }
|
||||
|
||||
# ── Google Mobile Ads (AdMob) ─────────────────────────────────────────────────
|
||||
-keep class com.google.android.gms.ads.** { *; }
|
||||
|
||||
Reference in new issue
Block a user