From 822879f070d1bf863b1764d6fe0d1149fa67659d Mon Sep 17 00:00:00 2001 From: Mustafa Ozhan Date: Wed, 26 Aug 2026 23:52:13 +0200 Subject: [PATCH 1/3] [Oztechan/AdTrack#132] Wire production AdMob App ID injection + release secrets Adds the injection paths so real AdMob ids replace the test defaults for release, while debug/CI keep working with no config. - iOS App ID: was a hardcoded test literal with no injection path. Now Info.plist reads $(ADMOB_APP_ID) from Config.xcconfig (test default), and fastlane build_project overrides it from the ADMOB_APP_ID env var when present (release). - release.yml passes ADMOB_APP_ID + per-format ad unit id secrets as env, mirroring the OAuth vars; each falls back to Google's test id when the secret is unset. - Harden common secret(): a blank value (unset GitHub secret -> empty env var) now falls back to the default instead of an empty id. - docs/admob-go-live.md: the keys, where each is consumed, and how to set them. Android App ID + all ad unit ids already read env/secret with a test fallback, so they only need the secrets wired (documented). No console/secret values committed. Claude-Session: https://claude.ai/code/session_01BhF4tpJYkiDSSJVoe63Vtk --- .github/workflows/release.yml | 9 +++++++ common/build.gradle.kts | 4 ++- docs/admob-go-live.md | 40 ++++++++++++++++++++++++++++ iosApp/Configuration/Config.xcconfig | 3 +++ iosApp/fastlane/Fastfile | 6 ++++- iosApp/iosApp/Info.plist | 5 ++-- 6 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 docs/admob-go-live.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7ba9a1..b3d9ac2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,15 @@ on: env: GOOGLE_OAUTH_CLIENT_ID_ANDROID: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID_ANDROID }} GOOGLE_OAUTH_CLIENT_ID_IOS: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID_IOS }} + # AdMob App ID (Android manifest + iOS Info.plist) and per-format ad unit ids. All fall back to + # Google's public test ids when the secret is unset, so builds work without configuration. + ADMOB_APP_ID: ${{ secrets.ADMOB_APP_ID }} + ADMOB_BANNER_UNIT_ID_ANDROID: ${{ secrets.ADMOB_BANNER_UNIT_ID_ANDROID }} + ADMOB_BANNER_UNIT_ID_IOS: ${{ secrets.ADMOB_BANNER_UNIT_ID_IOS }} + ADMOB_REWARDED_UNIT_ID_ANDROID: ${{ secrets.ADMOB_REWARDED_UNIT_ID_ANDROID }} + ADMOB_REWARDED_UNIT_ID_IOS: ${{ secrets.ADMOB_REWARDED_UNIT_ID_IOS }} + ADMOB_INTERSTITIAL_UNIT_ID_ANDROID: ${{ secrets.ADMOB_INTERSTITIAL_UNIT_ID_ANDROID }} + ADMOB_INTERSTITIAL_UNIT_ID_IOS: ${{ secrets.ADMOB_INTERSTITIAL_UNIT_ID_IOS }} SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} APP_STORE_CONNECT_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_KEY_CONTENT }} diff --git a/common/build.gradle.kts b/common/build.gradle.kts index d7adad8..c2d78b5 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -23,8 +23,10 @@ val secretProps = Properties().apply { if (file.exists()) file.inputStream().use { load(it) } } +// A blank source (e.g. an unset GitHub secret expands to an empty env var) falls back to [default]. fun secret(key: String, default: String): String = - secretProps.getProperty(key) ?: project.findProperty(key)?.toString() ?: System.getenv(key) ?: default + (secretProps.getProperty(key) ?: project.findProperty(key)?.toString() ?: System.getenv(key)) + ?.ifBlank { null } ?: default // Native OAuth clients are per-platform (separate Google Cloud client ids for Android & iOS). val oauthClientIdAndroid = secret( diff --git a/docs/admob-go-live.md b/docs/admob-go-live.md new file mode 100644 index 0000000..99e0683 --- /dev/null +++ b/docs/admob-go-live.md @@ -0,0 +1,40 @@ +# AdMob production IDs (go-live) + +Every AdMob id ships with Google's **public test id** as a default, so debug/CI builds work with no +configuration. Before shipping real ads, provide the real ids as **GitHub Actions secrets** (release +CI) and, for local release builds, in `secret.properties` (git-ignored). + +## Keys + +| Key | Consumed by | Test default | +| --- | --- | --- | +| `ADMOB_APP_ID` | Android manifest (`Key.ADMOB_APP_ID`) + iOS `Info.plist` `GADApplicationIdentifier` | `ca-app-pub-3940256099942544~1458002511` | +| `ADMOB_BANNER_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_BANNER_UNIT_ID` | banner test unit | +| `ADMOB_REWARDED_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_REWARDED_UNIT_ID` | rewarded test unit | +| `ADMOB_INTERSTITIAL_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_INTERSTITIAL_UNIT_ID` | interstitial test unit | + +The App ID has one value per app; the unit ids are per platform (`_ANDROID` / `_IOS`). + +## How to wire the real ids + +1. **AdMob console** — create the Android and iOS apps, then create one banner, rewarded, and + interstitial ad unit per platform. Copy the App IDs (`ca-app-pub-…~…`) and unit ids (`…/…`). +2. **GitHub Actions secrets** — add each key above under repo Settings → Secrets. `release.yml` + already passes them as env; each falls back to the test id when the secret is unset. +3. **Local release builds** — add the same keys to `secret.properties`, e.g.: + ```properties + ADMOB_APP_ID=ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY + ADMOB_BANNER_UNIT_ID_ANDROID=ca-app-pub-XXXXXXXXXXXXXXXX/AAAAAAAAAA + ADMOB_BANNER_UNIT_ID_IOS=ca-app-pub-XXXXXXXXXXXXXXXX/BBBBBBBBBB + # …rewarded / interstitial, per platform + ``` + +## Notes + +- **Android App ID** flows through `secret(Key.ADMOB_APP_ID)` → `manifestPlaceholders["admobAppId"]`; + empty env falls back to `secret.properties`, then the test default. +- **iOS App ID** is injected via the `ADMOB_APP_ID` build setting: `Config.xcconfig` holds the test + default, `Info.plist` reads `$(ADMOB_APP_ID)`, and fastlane's `build_project` overrides it from the + `ADMOB_APP_ID` env var when present (release only). +- **Unit ids** are read in `common/build.gradle.kts` via `secret(...)` into `BuildKonfig`; an unset + (blank) env var falls back to the test unit. diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index 421ad17..dc96379 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -1,3 +1,6 @@ TEAM_ID= BUNDLE_ID=com.oztechan.adtrack APP_NAME=AdTrack +// AdMob App ID injected into Info.plist as $(ADMOB_APP_ID). Google's public test App ID by default; +// the real one is passed as a build setting at release time (see fastlane build_project xcargs). +ADMOB_APP_ID=ca-app-pub-3940256099942544~1458002511 diff --git a/iosApp/fastlane/Fastfile b/iosApp/fastlane/Fastfile index 62d5705..3563bc8 100644 --- a/iosApp/fastlane/Fastfile +++ b/iosApp/fastlane/Fastfile @@ -144,12 +144,16 @@ platform :ios do update_project_provisioning( profile: ENV["sigh_#{identifier}_#{match_type}_profile-path"] ) + # Override the AdMob App ID (Info.plist reads $(ADMOB_APP_ID)) with the real one when + # provided; otherwise Config.xcconfig's test default is used. + admob_app_id = ENV["ADMOB_APP_ID"].to_s + admob_xcarg = admob_app_id.empty? ? "" : " ADMOB_APP_ID=#{admob_app_id}" gym( project: "iosApp.xcodeproj", scheme: "iosApp", configuration: options[:configuration], output_name: "AdTrack", - xcargs: "-allowProvisioningUpdates CODE_SIGN_STYLE=Manual", + xcargs: "-allowProvisioningUpdates CODE_SIGN_STYLE=Manual#{admob_xcarg}", codesigning_identity: "Apple Distribution: Mustafa Ozhan (Q5WB95G58X)", export_method: method, export_options: { diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index 279eb75..d533357 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -12,9 +12,10 @@ 6.0 CFBundleName AdTrack - + GADApplicationIdentifier - ca-app-pub-3940256099942544~1458002511 + $(ADMOB_APP_ID) NSUserTrackingUsageDescription Allow tracking to see ads more relevant to you. You can still use AdTrack fully either way. From 673bba1ecb9105f1cf52d65bb77ef545aece088c Mon Sep 17 00:00:00 2001 From: Mustafa Ozhan Date: Thu, 27 Aug 2026 00:05:06 +0200 Subject: [PATCH 2/3] [Oztechan/AdTrack#132] Split AdMob App ID per platform (Android vs iOS) The App ID is per-app in the AdMob console, so Android and iOS need different ones. Replace the single ADMOB_APP_ID secret with ADMOB_APP_ID_ANDROID and ADMOB_APP_ID_IOS (matching the per-platform unit ids). Defaults were already the two distinct Google test App IDs; only the secret/env key was shared. - Key.ADMOB_APP_ID -> Key.ADMOB_APP_ID_ANDROID (Android manifest). - fastlane build_project reads ADMOB_APP_ID_IOS for the iOS build-setting override. - release.yml passes both per-platform secrets; docs updated. Claude-Session: https://claude.ai/code/session_01BhF4tpJYkiDSSJVoe63Vtk --- .github/workflows/release.yml | 7 ++++--- androidApp/build.gradle.kts | 4 ++-- buildSrc/src/main/kotlin/config/key/Key.kt | 7 ++++--- docs/admob-go-live.md | 17 ++++++++++------- iosApp/Configuration/Config.xcconfig | 5 +++-- iosApp/fastlane/Fastfile | 6 +++--- 6 files changed, 26 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3d9ac2..0b08087 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,9 +8,10 @@ on: env: GOOGLE_OAUTH_CLIENT_ID_ANDROID: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID_ANDROID }} GOOGLE_OAUTH_CLIENT_ID_IOS: ${{ secrets.GOOGLE_OAUTH_CLIENT_ID_IOS }} - # AdMob App ID (Android manifest + iOS Info.plist) and per-format ad unit ids. All fall back to - # Google's public test ids when the secret is unset, so builds work without configuration. - ADMOB_APP_ID: ${{ secrets.ADMOB_APP_ID }} + # AdMob App IDs (per-platform: Android manifest + iOS Info.plist) and per-format ad unit ids. All + # fall back to Google's public test ids when the secret is unset, so builds work without config. + ADMOB_APP_ID_ANDROID: ${{ secrets.ADMOB_APP_ID_ANDROID }} + ADMOB_APP_ID_IOS: ${{ secrets.ADMOB_APP_ID_IOS }} ADMOB_BANNER_UNIT_ID_ANDROID: ${{ secrets.ADMOB_BANNER_UNIT_ID_ANDROID }} ADMOB_BANNER_UNIT_ID_IOS: ${{ secrets.ADMOB_BANNER_UNIT_ID_IOS }} ADMOB_REWARDED_UNIT_ID_ANDROID: ${{ secrets.ADMOB_REWARDED_UNIT_ID_ANDROID }} diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 50ca85b..6b485a1 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -30,8 +30,8 @@ android { // Must match OAUTH_REDIRECT_SCHEME so the manifest redirect intent-filter resolves. manifestPlaceholders["oauthRedirectScheme"] = "com.oztechan.adtrack" - // AdMob App ID injected into the manifest meta-data; test App ID by default (see Key). - manifestPlaceholders["admobAppId"] = secret(Key.ADMOB_APP_ID) + // AdMob App ID injected into the manifest meta-data; Android test App ID by default (see Key). + manifestPlaceholders["admobAppId"] = secret(Key.ADMOB_APP_ID_ANDROID) } signingConfigs { diff --git a/buildSrc/src/main/kotlin/config/key/Key.kt b/buildSrc/src/main/kotlin/config/key/Key.kt index 45a67ee..62a5534 100644 --- a/buildSrc/src/main/kotlin/config/key/Key.kt +++ b/buildSrc/src/main/kotlin/config/key/Key.kt @@ -11,7 +11,8 @@ enum class Key( ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD, - // AdMob App ID for the Android manifest. Defaults to Google's public sample App ID so debug - // builds work without secrets; the real one comes from secret.properties / env. - ADMOB_APP_ID("ca-app-pub-3940256099942544~3347511713") + // AdMob App ID for the Android manifest (per-platform: the iOS app has its own App ID). Defaults + // to Google's public Android test App ID so debug builds work without secrets; the real one comes + // from secret.properties / env. + ADMOB_APP_ID_ANDROID("ca-app-pub-3940256099942544~3347511713") } diff --git a/docs/admob-go-live.md b/docs/admob-go-live.md index 99e0683..e6289a1 100644 --- a/docs/admob-go-live.md +++ b/docs/admob-go-live.md @@ -8,12 +8,14 @@ CI) and, for local release builds, in `secret.properties` (git-ignored). | Key | Consumed by | Test default | | --- | --- | --- | -| `ADMOB_APP_ID` | Android manifest (`Key.ADMOB_APP_ID`) + iOS `Info.plist` `GADApplicationIdentifier` | `ca-app-pub-3940256099942544~1458002511` | +| `ADMOB_APP_ID_ANDROID` | Android manifest (`Key.ADMOB_APP_ID_ANDROID`) | `ca-app-pub-3940256099942544~3347511713` | +| `ADMOB_APP_ID_IOS` | iOS `Info.plist` `GADApplicationIdentifier` (via `$(ADMOB_APP_ID)`) | `ca-app-pub-3940256099942544~1458002511` | | `ADMOB_BANNER_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_BANNER_UNIT_ID` | banner test unit | | `ADMOB_REWARDED_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_REWARDED_UNIT_ID` | rewarded test unit | | `ADMOB_INTERSTITIAL_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_INTERSTITIAL_UNIT_ID` | interstitial test unit | -The App ID has one value per app; the unit ids are per platform (`_ANDROID` / `_IOS`). +Everything is **per platform** (`_ANDROID` / `_IOS`): the AdMob console registers the Android app and +the iOS app separately, so each has its own App ID as well as its own ad unit ids. ## How to wire the real ids @@ -23,18 +25,19 @@ The App ID has one value per app; the unit ids are per platform (`_ANDROID` / `_ already passes them as env; each falls back to the test id when the secret is unset. 3. **Local release builds** — add the same keys to `secret.properties`, e.g.: ```properties - ADMOB_APP_ID=ca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY - ADMOB_BANNER_UNIT_ID_ANDROID=ca-app-pub-XXXXXXXXXXXXXXXX/AAAAAAAAAA - ADMOB_BANNER_UNIT_ID_IOS=ca-app-pub-XXXXXXXXXXXXXXXX/BBBBBBBBBB + ADMOB_APP_ID_ANDROID=ca-app-pub-XXXXXXXXXXXXXXXX~AAAAAAAAAA + ADMOB_APP_ID_IOS=ca-app-pub-XXXXXXXXXXXXXXXX~BBBBBBBBBB + ADMOB_BANNER_UNIT_ID_ANDROID=ca-app-pub-XXXXXXXXXXXXXXXX/CCCCCCCCCC + ADMOB_BANNER_UNIT_ID_IOS=ca-app-pub-XXXXXXXXXXXXXXXX/DDDDDDDDDD # …rewarded / interstitial, per platform ``` ## Notes -- **Android App ID** flows through `secret(Key.ADMOB_APP_ID)` → `manifestPlaceholders["admobAppId"]`; +- **Android App ID** flows through `secret(Key.ADMOB_APP_ID_ANDROID)` → `manifestPlaceholders["admobAppId"]`; empty env falls back to `secret.properties`, then the test default. - **iOS App ID** is injected via the `ADMOB_APP_ID` build setting: `Config.xcconfig` holds the test default, `Info.plist` reads `$(ADMOB_APP_ID)`, and fastlane's `build_project` overrides it from the - `ADMOB_APP_ID` env var when present (release only). + `ADMOB_APP_ID_IOS` env var when present (release only). - **Unit ids** are read in `common/build.gradle.kts` via `secret(...)` into `BuildKonfig`; an unset (blank) env var falls back to the test unit. diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index dc96379..1b969e2 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -1,6 +1,7 @@ TEAM_ID= BUNDLE_ID=com.oztechan.adtrack APP_NAME=AdTrack -// AdMob App ID injected into Info.plist as $(ADMOB_APP_ID). Google's public test App ID by default; -// the real one is passed as a build setting at release time (see fastlane build_project xcargs). +// AdMob App ID injected into Info.plist as $(ADMOB_APP_ID). Google's public iOS test App ID by +// default; the real iOS App ID is passed as a build setting at release time from the +// ADMOB_APP_ID_IOS secret (see fastlane build_project xcargs). ADMOB_APP_ID=ca-app-pub-3940256099942544~1458002511 diff --git a/iosApp/fastlane/Fastfile b/iosApp/fastlane/Fastfile index 3563bc8..f8d7554 100644 --- a/iosApp/fastlane/Fastfile +++ b/iosApp/fastlane/Fastfile @@ -144,9 +144,9 @@ platform :ios do update_project_provisioning( profile: ENV["sigh_#{identifier}_#{match_type}_profile-path"] ) - # Override the AdMob App ID (Info.plist reads $(ADMOB_APP_ID)) with the real one when - # provided; otherwise Config.xcconfig's test default is used. - admob_app_id = ENV["ADMOB_APP_ID"].to_s + # Override the AdMob App ID (Info.plist reads $(ADMOB_APP_ID)) with the real iOS one when + # provided; otherwise Config.xcconfig's test default is used. (The App ID is per-platform.) + admob_app_id = ENV["ADMOB_APP_ID_IOS"].to_s admob_xcarg = admob_app_id.empty? ? "" : " ADMOB_APP_ID=#{admob_app_id}" gym( project: "iosApp.xcodeproj", From d5bc1d4b9c41fef101100f54cc1fe682a34d6fec Mon Sep 17 00:00:00 2001 From: Mustafa Ozhan Date: Thu, 27 Aug 2026 00:39:18 +0200 Subject: [PATCH 3/3] [Oztechan/AdTrack#132] Drop the go-live doc Claude-Session: https://claude.ai/code/session_01BhF4tpJYkiDSSJVoe63Vtk --- docs/admob-go-live.md | 43 ------------------------------------------- 1 file changed, 43 deletions(-) delete mode 100644 docs/admob-go-live.md diff --git a/docs/admob-go-live.md b/docs/admob-go-live.md deleted file mode 100644 index e6289a1..0000000 --- a/docs/admob-go-live.md +++ /dev/null @@ -1,43 +0,0 @@ -# AdMob production IDs (go-live) - -Every AdMob id ships with Google's **public test id** as a default, so debug/CI builds work with no -configuration. Before shipping real ads, provide the real ids as **GitHub Actions secrets** (release -CI) and, for local release builds, in `secret.properties` (git-ignored). - -## Keys - -| Key | Consumed by | Test default | -| --- | --- | --- | -| `ADMOB_APP_ID_ANDROID` | Android manifest (`Key.ADMOB_APP_ID_ANDROID`) | `ca-app-pub-3940256099942544~3347511713` | -| `ADMOB_APP_ID_IOS` | iOS `Info.plist` `GADApplicationIdentifier` (via `$(ADMOB_APP_ID)`) | `ca-app-pub-3940256099942544~1458002511` | -| `ADMOB_BANNER_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_BANNER_UNIT_ID` | banner test unit | -| `ADMOB_REWARDED_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_REWARDED_UNIT_ID` | rewarded test unit | -| `ADMOB_INTERSTITIAL_UNIT_ID_ANDROID` / `_IOS` | `BuildKonfig.ADMOB_INTERSTITIAL_UNIT_ID` | interstitial test unit | - -Everything is **per platform** (`_ANDROID` / `_IOS`): the AdMob console registers the Android app and -the iOS app separately, so each has its own App ID as well as its own ad unit ids. - -## How to wire the real ids - -1. **AdMob console** — create the Android and iOS apps, then create one banner, rewarded, and - interstitial ad unit per platform. Copy the App IDs (`ca-app-pub-…~…`) and unit ids (`…/…`). -2. **GitHub Actions secrets** — add each key above under repo Settings → Secrets. `release.yml` - already passes them as env; each falls back to the test id when the secret is unset. -3. **Local release builds** — add the same keys to `secret.properties`, e.g.: - ```properties - ADMOB_APP_ID_ANDROID=ca-app-pub-XXXXXXXXXXXXXXXX~AAAAAAAAAA - ADMOB_APP_ID_IOS=ca-app-pub-XXXXXXXXXXXXXXXX~BBBBBBBBBB - ADMOB_BANNER_UNIT_ID_ANDROID=ca-app-pub-XXXXXXXXXXXXXXXX/CCCCCCCCCC - ADMOB_BANNER_UNIT_ID_IOS=ca-app-pub-XXXXXXXXXXXXXXXX/DDDDDDDDDD - # …rewarded / interstitial, per platform - ``` - -## Notes - -- **Android App ID** flows through `secret(Key.ADMOB_APP_ID_ANDROID)` → `manifestPlaceholders["admobAppId"]`; - empty env falls back to `secret.properties`, then the test default. -- **iOS App ID** is injected via the `ADMOB_APP_ID` build setting: `Config.xcconfig` holds the test - default, `Info.plist` reads `$(ADMOB_APP_ID)`, and fastlane's `build_project` overrides it from the - `ADMOB_APP_ID_IOS` env var when present (release only). -- **Unit ids** are read in `common/build.gradle.kts` via `secret(...)` into `BuildKonfig`; an unset - (blank) env var falls back to the test unit.