Compare commits

...
Author SHA1 Message Date
Thomas SharedInboxandClaude Sonnet 4.6 7f9bc65965 fix(android): disable AGP new-DSL mode to fix signReleaseBundle NullPointerException
AGP 9+ defaults to new-DSL-only mode, which causes a NullPointerException
when conditionally creating signingConfigs inside the android {} block.
Setting android.newDsl=false restores the previous evaluation behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:34:51 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 e76c536e0e fix: pass keystore password as Dagger secret to Android builds
ANDROID_KEYSTORE_PASSWORD was set in the CI runner environment but never
forwarded into the Dagger container, so System.getenv() returned null
inside the Flutter build, causing a NullPointerException in
FinalizeBundleTask when signing the release bundle.

- Add keystorePassword *dagger.Secret param to BuildAndroidRelease,
  BuildAndroidApk, PublishAndroid, and DeployApk in the Dagger module
- Pass ANDROID_KEYSTORE_PASSWORD via WithSecretVariable to the build container
- Update ci.yml to supply env:ANDROID_KEYSTORE_PASSWORD to both
  publish-android and deploy-apk dagger calls
- Refactor build.gradle.kts to conditionally create the signing config
  only when both the keystore file and password are available, avoiding
  null values being passed to the signing config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 11:18:00 +02:00
4 changed files with 24 additions and 18 deletions
+2 -2
View File
@@ -97,7 +97,7 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
PLAY_STORE_CONFIG_JSON: ${{ secrets.PLAY_STORE_CONFIG_JSON }}
run: |
nix develop --no-warn-dirty --command dagger call --progress=plain -m ci publish-android --source . --play-store-config env:PLAY_STORE_CONFIG_JSON
nix develop --no-warn-dirty --command dagger call --progress=plain -m ci publish-android --source . --play-store-config env:PLAY_STORE_CONFIG_JSON --keystore-password env:ANDROID_KEYSTORE_PASSWORD
- name: Build & Deploy APK to server
continue-on-error: true
@@ -108,7 +108,7 @@ jobs:
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
run: |
HASH=$(git rev-parse --short HEAD)
nix develop --no-warn-dirty --command dagger call --progress=plain -m ci deploy-apk --source . --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH"
nix develop --no-warn-dirty --command dagger call --progress=plain -m ci deploy-apk --source . --ssh-key env:SSH_PRIVATE_KEY --ssh-user "$SSH_USER" --ssh-host "$SSH_HOST" --commit-hash "$HASH" --keystore-password env:ANDROID_KEYSTORE_PASSWORD
publish-website:
name: Publish Website Build History
+13 -12
View File
@@ -20,15 +20,18 @@ android {
jvmTarget = JavaVersion.VERSION_17.toString()
}
signingConfigs {
create("release") {
// Hardcoded alias matching t.sh
keyAlias = "upload"
// Use the same password for both key and keystore
val pass = System.getenv("ANDROID_KEYSTORE_PASSWORD")
storePassword = pass
keyPassword = pass
storeFile = file("upload-keystore.jks")
val keystoreFile = file("upload-keystore.jks")
val keystorePass: String? = System.getenv("ANDROID_KEYSTORE_PASSWORD")
val hasKeystore = keystoreFile.exists() && keystorePass != null
if (hasKeystore) {
signingConfigs {
create("release") {
keyAlias = "upload"
storePassword = keystorePass
keyPassword = keystorePass
storeFile = keystoreFile
}
}
}
@@ -44,9 +47,7 @@ android {
buildTypes {
release {
// Use the signing config defined above for release builds.
// If the keystore file exists (e.g. in CI or manually placed), sign it.
signingConfig = if (signingConfigs.getByName("release").storeFile?.exists() == true) {
signingConfig = if (hasKeystore) {
signingConfigs.getByName("release")
} else {
signingConfigs.getByName("debug")
+1
View File
@@ -1,2 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.newDsl=false
+8 -4
View File
@@ -262,8 +262,9 @@ func (m *Ci) DeployLinux(
}
// Build and return the Android APK
func (m *Ci) BuildAndroidApk(source *dagger.Directory) *dagger.File {
func (m *Ci) BuildAndroidApk(source *dagger.Directory, keystorePassword *dagger.Secret) *dagger.File {
return m.Setup(source).
WithSecretVariable("ANDROID_KEYSTORE_PASSWORD", keystorePassword).
WithExec([]string{"flutter", "build", "apk", "--release"}).
File("build/app/outputs/flutter-apk/app-release.apk")
}
@@ -276,9 +277,10 @@ func (m *Ci) DeployApk(
sshUser string,
sshHost string,
commitHash string,
keystorePassword *dagger.Secret,
) (string, error) {
// 1. Build the APK
apk := m.BuildAndroidApk(source)
apk := m.BuildAndroidApk(source, keystorePassword)
// 2. Deploy
datePath := time.Now().Format("2006/01/02")
@@ -293,8 +295,9 @@ func (m *Ci) DeployApk(
}
// Build and return the Android App Bundle (AAB)
func (m *Ci) BuildAndroidRelease(source *dagger.Directory) *dagger.File {
func (m *Ci) BuildAndroidRelease(source *dagger.Directory, keystorePassword *dagger.Secret) *dagger.File {
return m.Setup(source).
WithSecretVariable("ANDROID_KEYSTORE_PASSWORD", keystorePassword).
WithExec([]string{"flutter", "build", "appbundle", "--release"}).
File("build/app/outputs/bundle/release/app-release.aab")
}
@@ -304,9 +307,10 @@ func (m *Ci) PublishAndroid(
ctx context.Context,
source *dagger.Directory,
playStoreConfig *dagger.Secret,
keystorePassword *dagger.Secret,
) (string, error) {
// 1. Build the AAB
aab := m.BuildAndroidRelease(source)
aab := m.BuildAndroidRelease(source, keystorePassword)
// 2. Prepare script source
scriptSource := source.Filter(dagger.DirectoryFilterOpts{