YiBi Dev Docs
架構決策 (ADR)

Firebase 治理:Terraform 管結構、CLI/CI 管部署、config 改 secret 注入

Context

本專案的 Firebase 與 GCP 是兩個獨立 project:GCP project 為 yibi-mvp(承載 Cloud Run / Cloud SQL / GCS / Terraform state),Firebase project 為 myyibi(project number 238382742724, 承載 App Distribution 與規劃中的 FCM)。

目前 Firebase 的治理處於半手動、無紀錄、單一環境未分離狀態,缺一份決策文件把「什麼歸誰管」攤開:

面向現狀問題
Terraform 覆蓋僅 1 條 roles/firebase.admin IAM binding(infra/terraform/modules/iam/main.tf:52,綁在 myyibiFirebase project 啟用、App 註冊全靠 Console 手動,無版本紀錄、新人難重建
Providerinfra/terraform/environments/*/versions.tf 只宣告 google ~> 7.35google-beta多數 google_firebase_* resource 需要 google-beta,現在連 plan 都跑不起來
Android configmobile/android/app/google-services.json 已 gitignoremobile/.gitignore:67-70,未進 git)+ CI 用 base64 secret GOOGLE_SERVICES_JSON 解碼注入(.github/workflows/cd-mobile.yml:95Android 其實已正確落地 L4(gitignore + secret 注入);無雙軌問題
iOS configmobile/ios/Runner/GoogleService-Info.plist 已 gitignore(mobile/.gitignore:70)但無對應 secret / 注入步驟docs/runbook/deployment/05-firebase-verify.sh:53 期望其存在)iOS 路徑不完整:缺 secret + CI 注入,與 Android 不對稱
環境隔離單一 Firebase project myyibi 通吃所有環境(cd-mobile.yml:143 --project myyibi;beta build 指向 staging backend 卻共用同一 Firebase project)未對齊 GCP 端早已分離的 infra/terraform/environments/{staging,prod};Analytics / Crashlytics / FCM sender / API key 全環境混用
App Distributioncd-mobile.yml 用 firebase CLI firebase appdistribution:distribute(非 fastlane),target beta-families tester group可接受;Terraform 本就管不到 release 動作
FCM推通知合約已定(docs/features/010-mobile-app/contracts/push-notification-events.yaml),後端發送實作未落地;mobile pubspec.yaml 無任何 firebase_* 依賴規劃中,roadmap 需釐清何時、用什麼方式納管

目標:讓 Firebase 結構性設定可版本控制(config-as-code)、把 Web Console 的人為操作降到最低、 新人能從 repo + CI 重建環境。本 ADR 不變更任何程式碼,只確立治理分層原則,實作待裁決後另開 change/PR。

範圍備註:Firebase Auth(identity broker)的啟用與 provider 設計另由 ADR-0016(PR #573, spectra change 0032)獨立裁決。本 ADR 只定義 Auth 一旦啟用後「歸哪一層治理」(L1 Terraform), 不重複決定是否採用 Firebase Auth。

Decision

本節為提案(proposed),非既定事實。 推薦方案為 B;最終方向待 howie 於 review 裁決後轉 accepted

結論(提案):採業界主流的 Firebase 三層分工治理。核心原則是 Firebase CLI 與 Terraform 互補、不重疊——「資源的存在性」歸 Terraform(宣告式、有 state),「發佈/部署動作」歸 CLI/CI (命令式、無 state)。Firebase CLI 本身不是 Terraform 的管理對象,而是被 CI script 呼叫的工具。

工具性質管什麼本專案落地
L1 基礎設施Terraform(google-beta宣告式 / 有 stateFirebase project 啟用、App 註冊(Android/iOS)、Auth providers(見 ADR-0016)、Firestore/Storage、Security Rules、IAM引入 google-beta provider、provider 指向 myyibiterraform import 現有 Android/iOS app
L2 部署Firebase CLI(CI script 呼叫)命令式 / 無 stateCloud Functions、Security Rules deploy、Firestore indexes維持由 CI 呼叫,不被 Terraform 管理
L3 行動發佈firebase CLI / fastlane(CI)命令式 / 無 stateApp Distribution release、tester group維持現有 cd-mobile.yml(firebase CLI)
L4 config 檔GCP Secret Manager + CI 注入google-services.json / GoogleService-Info.plistAndroid 已 gitignore + secret 注入;已裁決改存 GCP Secret Manager(per-env,取代散落的 GitHub Secret);補齊 iOS plist 對應 secret

直接回答常見疑問:

  • 「Firebase CLI 是用 Terraform 還是 script 管理?」 — 用 script(CI)。CLI 是執行動作的命令式工具, 與宣告式的 Terraform 互補;兩者不互相管理。
  • Terraform 管不到的項目(必須留在 CLI/CI 或 Console):App Distribution release (Terraform open feature request #16604)、 Remote Config(#10886)、 Firebase 服務條款接受(僅 Console,一次性)、Analytics 設定(多數僅 Console)。

Per-environment 原則(提案)

Firebase 應對齊 GCP 端早已存在的多環境分離(infra/terraform/environments/{staging,prod})。 Firebase 最佳實踐是一環境一 project(隔離 Analytics / Crashlytics / FCM sender / API key, 避免 dev/test 流量污染 prod 數據、避免共用憑證):

  • L1 以 for_each over environment 在 Terraform 管多個 Firebase project(如 yibi-stagingyibi-prod), 比照既有 environments/ 模式。
  • 每環境各一份 google-services.json / GoogleService-Info.plist,由 L4 的 secret 機制按環境注入。
  • 是否新增獨立 dev 環境、或保留單一 project + build flavor 區隔,列為 Open Question 待裁決。

config 檔的性質與保管(已裁決:GCP Secret Manager)

重點澄清google-services.json / GoogleService-Info.plist環境設定、非 server secret。 其中的 api_key 是客戶端識別碼,會被打包進 APK / IPA、可被反編譯取出。Firebase 的安全不靠隱藏此檔, 而靠 Security Rules + App Check + GCP Console 的 API key 限制(綁 package name / SHA、限定 scope)。 gitignore 它是衛生(避免散落、環境混淆),而非保密。

真正必須嚴格保管的 secret(皆已正確處理):

真正的 secret現況保管
Deployer SA / WIFGitHub Secret(WIF,無下載式金鑰)
Android keystore + 密碼GitHub Secret ANDROID_KEYSTORE_BASE64
Terraform origin CA cert/keyGCP Secret Manager

裁決(howie & keith)google-services.jsonGoogleService-Info.plist 一律存 GCP Secret Manager (貼合既有 infra——origin CA cert 已在此、Terraform 可讀、IAM 可稽核),CI 由 Secret Manager 取出注入, 取代目前散落的 GitHub Secret GOOGLE_SERVICES_JSON;per-env 各一份(如 firebase-google-services-staging / -prod)。 不採 1Password——它不是真 secret,且 CI 端不如 Secret Manager 原生(1Password 至多作為開發者本機取用的便利來源)。

Local 開發 Firebase 策略(已裁決:不新增 dev project)

裁決(howie)不為 local 開發新增獨立 dev Firebase project;本地需求用 emulator + stub +(必要時)直連 staging 覆蓋。各服務分流如下:

服務Local 策略說明
App Distribution不涉及本地 flutter run 直接跑,不經過 App Distribution
Firebase Auth(ADR-0016)Emulator 優先firebase emulators:start --only authlocalhost:9099),mobile SDK 與 backend Admin SDK 皆指向 emulator,離線、免真 project、可造假用戶。整合測試時才直連 staging
Firebase Auth — bypass可選因 ADR-0016 為 token-exchange 架構(Firebase token → 自家 JWT),backend 可開 dev-only 登入路徑直接發 YiBi JWT,mobile 以 --dart-define 切 fake-login,完全不碰 Firebase
FCM 推播本地 stubEmulator Suite 不模擬真實推播投遞;本地把發送 stub 成 log,需驗實機收訊時才連 staging

要點:Firebase Emulator Suite 支援 Auth/Firestore/Functions/Storage/RTDB,但不支援 FCM 推播投遞、App Distribution、Analytics、Crashlytics。因此 local 策略是「Auth 走 emulator、FCM 走 stub、App Distribution 無關」,不需要第三個 Firebase 環境。

Alternatives Considered

  • 方案 A:維持現狀(最小 Terraform + Console 手動) — 考慮過,因為 零遷移成本;但 App 註冊與 Firebase project 啟用全無紀錄、持續依賴 Console、新人重建困難、且環境未分離(單一 myyibi 通吃), 不符「減少 Console、config-as-code、環境隔離」目標,沒有選。
  • 方案 B:三層分工 + 多環境(推薦) — 結構性資源 IaC、Console 操作降到僅剩一次性/不可管項目、release 維持 輕量 CLI 不過度工程化、Firebase project 按環境分離;代價是引入並維護 google-beta provider、需 terraform import 既有 app、per-env project 的初始設定成本。
  • 方案 C:全面 IaC + fastlane 化 — 考慮過,在方案 B 之上把 App Distribution 從 firebase CLI 換成 fastlane plugin(firebase_app_distribution)並 code 化 tester group;但 release 本質是 imperative 動作、code 化收益有限,且 fastlane 引入/維護成本高(現行 CLI 流程已可運作),沒有選為預設。

Consequences

  • Firebase 結構性設定(project、app 註冊、Auth provider、Rules)可進版本控制與 PR review。
  • Web Console 的人為操作降到僅剩一次性(條款接受)與 Terraform 不可管項目(Remote Config / Analytics)。
  • 新人可從 Terraform + CI 重建 Firebase 環境,不需口耳相傳的 Console 步驟。
  • config 檔保管收斂為單一真相(GCP Secret Manager,per-env);環境間 Analytics / FCM / API key 互不污染。

壞 / 代償

  • 需在 versions.tf 引入並長期維護 google-beta provider,跨 myyibi project 的 provider 設定增加複雜度。
  • 既有 Android/iOS app 必須 terraform import 接管,否則 apply 會試圖重建既有資源。
  • 改用 per-env Firebase project 需一次性建立 staging/prod project 並逐一註冊 app、產生對應 config。
  • config 已 gitignore(非新增限制),但改存 Secret Manager 後本地開發需先從 Secret Manager 拉取(或 flutterfire configure)才能 build。

中性

  • App Distribution release 與 Cloud Functions deploy 仍是 imperative 的 CI 動作,terraform plan 永遠不會偵測其漂移——這是預期的分工結果,非缺陷。

Open Questions / Roadmap(全面治理範圍)

  1. FCM:後端推通知實作何時補齊;FCM 多數設定靠 Admin SDK runtime(非 IaC),需釐清哪些(若有)值得進 Terraform。
  2. Firebase Auth:是否啟用由 ADR-0016 / change 0032 裁決;若採用,其 provider 設定納入本 ADR 的 L1(Terraform google_identity_platform_*)。
  3. Firestore / Storage:目前未使用;若啟用則納入 L1,Security Rules 走 L2(firebase CLI)。
  4. fastlane 升級:是否從方案 B 進一步走方案 C(App Distribution 改 fastlane),待行動 CI 成熟度評估。
  5. config 保管:✅ 已裁決存 GCP Secret Manager(見 Decision)。未來優化(非阻塞):app 經 Terraform 註冊後改用 flutterfire configure / data source google_firebase_android_app_config 重新生成,達零保管。
  6. iOS plist 缺失:補齊 GoogleService-Info.plist、建立對應 Secret Manager secret + CI 注入(與 Android 對稱)。
  7. 環境切分粒度:✅ 已裁決——per-env Firebase project 僅 staging + prod不新增獨立 dev project;local 開發以 Auth emulator + FCM stub +(必要時)直連 staging 覆蓋(見 Decision「Local 開發 Firebase 策略」)。
  8. 一次性 Console 項目記錄:Firebase 條款接受、Remote Config 等不可 IaC 的步驟,應在 docs/runbook/deployment/ 明確記錄,避免被誤認為可自動化。
  9. Google Play 上傳(範圍外,另立 ADR):本 ADR 範圍是 Firebase project myyibi不涵蓋 Google Play(Play Console 是與 Firebase 獨立的系統)。AAB 上傳 Google Play 的 keyless WIF 決策另見 ADR-0020(accepted);其與本 ADR 正交、可獨立執行,唯一交集是共用既有 WIF/deployer SA 機制與 cd-mobile.yml

On this page