Skip to content

feat: implement secure APK signing for GitHub Actions - #279

Merged
grunch merged 14 commits into
mainfrom
added/sign-release
Aug 25, 2025
Merged

feat: implement secure APK signing for GitHub Actions#279
grunch merged 14 commits into
mainfrom
added/sign-release

Conversation

@grunch

@grunch grunch commented Aug 24, 2025

Copy link
Copy Markdown
Member
  • Add comprehensive GitHub Secrets setup guide with keystore creation instructions
  • Configure conditional signing system with production keystore fallback to debug
  • Update build.gradle with secure credential loading and environment variable support
  • Add GitHub Actions workflow steps for keystore decoding and verification
  • Implement dual storage architecture for development and CI environments
  • Include troubleshooting guide and security best practices documentation
  • Fix APK installation issues by ensuring consistent signing certificates

Summary by CodeRabbit

  • Chores

    • Integrated Android release signing into CI: injects keystore, exports signing credentials to build steps, adds signing verification, and securely cleans up keystore artifacts.
    • Made release signing conditional: use provided keystore when available; fall back to debug signing otherwise.
    • Narrowed ignore rules to explicitly exclude keystore and key property files.
    • Added example keystore properties template for local setup.
  • Documentation

    • Added a step-by-step guide for configuring secrets and verifying signed Android builds in CI and locally.

- Add comprehensive GitHub Secrets setup guide with keystore creation instructions
- Configure conditional signing system with production keystore fallback to debug
- Update build.gradle with secure credential loading and environment variable support
- Add GitHub Actions workflow steps for keystore decoding and verification
- Implement dual storage architecture for development and CI environments
- Include troubleshooting guide and security best practices documentation
- Fix APK installation issues by ensuring consistent signing certificates
@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Adds CI steps to provision, use, verify, and clean an Android keystore during builds; updates Gradle to support conditional release signing from key.properties or environment with a debug fallback; narrows .gitignore keystore rules; and adds docs plus a key.properties example.

Changes

Cohort / File(s) Summary
CI: Android signing integration
.github/workflows/main.yml
Adds conditional (SHOULD_BUILD) Setup Android Keystore step (decodes base64 secret to android/app/upload-keystore.jks, exports ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEYSTORE_FILE, writes android/key.properties), injects signing env into Build APK and Build appBundle steps, adds Verify APK Signing step (jarsigner/apksigner checks, optional ANDROID_CERT_SHA256), and adds Cleanup Keystore step (removes android/app/upload-keystore.jks and android/key.properties with always()). Minor formatting tweak in Create Release step.
Android Gradle signing logic
android/app/build.gradle
Adds loading of keystoreProperties and environment fallback (envKeystore), computes hasKeystore from file or env, defines conditional signingConfigs.release sourcing from file or env, and updates buildTypes.release to apply release signing if available or fall back to signingConfigs.debug with a warning.
Docs: GitHub secrets setup
GITHUB_SECRETS_SETUP.md
New guide documenting keystore creation, local verification, base64 encoding for secrets, required repo secrets (ANDROID_KEYSTORE_FILE, ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_PASSWORD, ANDROID_KEY_ALIAS), CI verification, troubleshooting, and security best practices.
Examples: key.properties template
android/key.properties.example
New example template with placeholder storePassword, keyPassword, keyAlias, and storeFile for local development.
Git ignore adjustments
.gitignore
Replaces broad android/*.keystore ignore with explicit ignores for android/key.properties and android/app/upload-keystore.jks.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant GH as GitHub Actions
    participant Secrets as Repo Secrets
    participant FS as Workspace FS
    participant Build as Gradle/Flutter Build
    participant Tools as Verification Tools

    Note over GH: Build job (runs if SHOULD_BUILD)
    GH->>Secrets: Read ANDROID_KEYSTORE_FILE (base64), ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_PASSWORD, ANDROID_KEY_ALIAS
    GH->>FS: Decode keystore -> `android/app/upload-keystore.jks`
    GH->>FS: Write `android/key.properties` with store/key values
    GH->>Build: Run build with env: ANDROID_KEYSTORE_*, ANDROID_KEYSTORE_FILE

    rect rgba(230,245,255,0.6)
    note right of Build: Build APK / AAB (uses signing if hasKeystore)
    Build->>FS: Emit APK / AAB artifacts
    end

    GH->>FS: Check APK exists
    GH->>Tools: Run jarsigner (and apksigner if available) to verify signature (optional fingerprint check)
    alt Signed
        GH->>GH: Continue to release steps
    else Unsigned / missing
        GH->>GH: Fail verification
    end

    rect rgba(255,240,230,0.6)
    Note over FS: Cleanup (always)
    GH->>FS: Remove `android/app/upload-keystore.jks` and `android/key.properties`
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I hop with keys in fur so neat,
Base64 bundles tucked with care,
Gradle signs beneath my feet,
Jarsigner hums the evening air,
I thump—a quiet, clean release to share. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d14f9f6 and fbeb0e1.

📒 Files selected for processing (1)
  • GITHUB_SECRETS_SETUP.md (1 hunks)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch added/sign-release

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.gitignore (1)

43-45: Potentially dangerous pattern: *.gradle ignores Gradle build scripts repo-wide

While not part of this diff, keeping *.gradle will cause any newly-created Gradle scripts (e.g., build.gradle.kts or additional module .gradle files) to be ignored by Git. This can lead to missing files in future PRs. Suggest replacing it with directory-scoped ignores only (e.g., .gradle/ which you already have under Android).

Apply this diff to tighten the pattern:

-*.gradle
+.gradle/
🧹 Nitpick comments (7)
android/key.properties.example (1)

1-6: Tighten placeholders and add an explicit path hint for storeFile

Looks good overall. Two small improvements:

  • Prevent accidental copy of placeholders by using a clearly invalid alias and file name.
  • Align storeFile with where the workflow writes the keystore (android/app/upload-keystore.jks).
-# Example key.properties for local development
-# Copy this to key.properties and fill in your actual values
-storePassword=your_store_password_here
-keyPassword=your_key_password_here  
-keyAlias=your_key_alias_here
-storeFile=your_keystore_file_name.jks
+# Example key.properties for local development
+# Copy this to android/key.properties and replace ALL placeholder values
+storePassword=REPLACE_ME_STORE_PASSWORD
+keyPassword=REPLACE_ME_KEY_PASSWORD
+keyAlias=REPLACE_ME_KEY_ALIAS
+# If your keystore is located at android/app/upload-keystore.jks (default), keep this:
+storeFile=app/upload-keystore.jks
+# Otherwise, set a correct relative path from android/ (e.g., app/my-release.jks)
GITHUB_SECRETS_SETUP.md (4)

80-99: Cross‑platform base64 guidance

base64 -w 0 works on GNU coreutils (Linux) but fails on macOS. Provide a macOS-friendly variant to reduce setup friction.

-# Generate base64 (this creates one very long string)
-base64 -w 0 android/app/upload-keystore.jks
+# Generate base64 of the keystore as a single line
+# Linux (GNU coreutils):
+base64 -w 0 android/app/upload-keystore.jks
+# macOS (BSD base64, no -w flag):
+base64 android/app/upload-keystore.jks | tr -d '\n'

217-228: Add code fence language and avoid implying exact fingerprints

Marking code fences with a language fixes markdownlint MD040. Also, fingerprints shown here are examples; consider clarifying to prevent copy/paste mistakes.

-```
+```text
 Alias name: youraliasname
-Creation date: Aug 13, 2025
+Creation date: <Aug 13, 2025>
 Entry type: PrivateKeyEntry
 Certificate chain length: 1
 Certificate[1]:
 Owner: CN=Your App Name, OU=Organization, O=Company...
 Valid from: Wed Aug 13 18:47:47 UYT 2025 until: Sun Dec 29 18:47:47 UYT 2052
 Certificate fingerprints:
      SHA1: FD:DB:64:E4:7C:60:4D:BD:27:9F:A7:C2:D7:16:AB:6B:40:74:9A:F3
      SHA256: 67:6F:67:26:76:D3:59:3A:C5:2F:01:7C:58:1C:50:C0:8B...

---

`429-435`: **Avoid encouraging weak passwords in troubleshooting**

Suggest removing “Try common passwords” to avoid normalizing weak credential practices. Instead, direct users to check password managers/backups and original provisioning docs. 


```diff
-2. **Try common passwords**: `android`, `123456`, `password`, your app name
-3. **Check your notes/password manager** for the correct password
+2. **Check your notes/password manager** for the correct password
+3. **Consult your team’s provisioning/backups** for the recorded credentials

371-377: Add language to fenced code block (markdownlint MD040) and mask secrets in examples

Mark code block as text and avoid printing actual secret names inline to discourage copy/paste into logs.

-```
+```text
 Setting environment variables
 ANDROID_KEYSTORE_PASSWORD=***
 ANDROID_KEY_PASSWORD=***
 ANDROID_KEY_ALIAS=***
 ANDROID_KEYSTORE_FILE=upload-keystore.jks

</blockquote></details>
<details>
<summary>android/app/build.gradle (1)</summary><blockquote>

`65-74`: **Make the fallback explicit and fail fast for CI if signing is expected**

The warning is helpful for local dev, but CI should not unknowingly ship a debug-signed “release”. Consider failing the build in CI when hasKeystore=false. 


```diff
 release {
     if (hasKeystore) {
         signingConfig signingConfigs.release
     } else {
         // Fallback to debug signing for development
         signingConfig signingConfigs.debug
-        logger.warn("Release build using debug signing - keystore not available")
+        logger.warn("Release build using debug signing - keystore not available")
+        if (System.getenv('CI')?.toBoolean()) {
+            throw new GradleException("CI release build requires a release keystore")
+        }
     }
 }
.github/workflows/main.yml (1)

167-171: Also clean up key.properties; add newline at EOF

Remove the temporary android/key.properties after the build (secrets hygiene) and satisfy “no newline at EOF”.

-      - name: Cleanup Keystore
+      - name: Cleanup Keystore
         if: always() && steps.should_build.outputs.SHOULD_BUILD == 'true'
         run: |
           # Remove keystore file for security
-          rm -f android/app/upload-keystore.jks
+          rm -f android/app/upload-keystore.jks android/key.properties
+
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 32925a4 and c904432.

📒 Files selected for processing (5)
  • .github/workflows/main.yml (3 hunks)
  • .gitignore (1 hunks)
  • GITHUB_SECRETS_SETUP.md (1 hunks)
  • android/app/build.gradle (3 hunks)
  • android/key.properties.example (1 hunks)
🧰 Additional context used
🪛 LanguageTool
GITHUB_SECRETS_SETUP.md

[grammar] ~7-~7: There might be a mistake here.
Context: ...Guide Achieves By the end, you'll have: - ✅ Properly signed APKs built in GitHub A...

(QB_NEW_EN)


[grammar] ~8-~8: There might be a mistake here.
Context: ...erly signed APKs built in GitHub Actions - ✅ Consistent signing certificates across...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ...t signing certificates across all builds - ✅ Secure credential storage in GitHub Se...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...ure credential storage in GitHub Secrets - ✅ APK installation that works without un...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...hat You Need Before starting, you need: - ✅ Android keystore file (.jks or `.key...

(QB_NEW_EN)


[grammar] ~50-~50: There might be a mistake here.
Context: ...-key.jks` If you find keystore files: - ✅ You have a keystore → Skip to "Cre...

(QB_NEW_EN)


[grammar] ~53-~53: There might be a mistake here.
Context: ...found** → These won't work for release builds, create a new one **If no keystore files...

(QB_NEW_EN)


[grammar] ~53-~53: There might be a mistake here.
Context: ...ork for release builds, create a new one If no keystore files found: - ❌ **No ke...

(QB_NEW_EN)


[grammar] ~55-~55: There might be a mistake here.
Context: ...a new one If no keystore files found: - ❌ No keystore exists → Continue to "...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...ds (choose strong, memorable passwords):** - Store Password: Protects the keystore ...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...ssword**: Protects the signing key (can be same as store password) Key Alias:...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...: Protects the signing key (can be same as store password) Key Alias: A name ...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...tion (these identify your organization):** - CN (Common Name): Your app name or org...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...ganization (e.g., MyApp, My Company) - OU (Organizational Unit): Your departm...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...g., Mobile Development, Engineering) - O (Organization): Your company name (e...

(QB_NEW_EN)


[grammar] ~75-~75: There might be a mistake here.
Context: ...our company name (e.g., MyCompany Inc) - L (Locality): Your city (e.g., `San Fr...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...ty)**: Your city (e.g., San Francisco) - ST (State): Your state/province (e.g.,...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...Your state/province (e.g., California) - C (Country): Your country code (e.g., ...

(QB_NEW_EN)


[grammar] ~100-~100: There might be a mistake here.
Context: ...OUR_COUNTRY" ``` Command Explanation: - -keystore upload-keystore.jks: Creates file named `upload-keystore.jk...

(QB_NEW_EN)


[grammar] ~101-~101: There might be a mistake here.
Context: ...pload-keystore.jks: Creates file named upload-keystore.jks--keyalg RSA -keysize 2048`: Uses RSA encryption with 2048-bit key ...

(QB_NEW_EN)


[grammar] ~102-~102: There might be a mistake here.
Context: ...SA encryption with 2048-bit key (secure) - -validity 10000: Certificate valid for ~27 years - `-al...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ... 10000: Certificate valid for ~27 years - -alias YOUR_CHOSEN_ALIAS`: Replace with your chosen alias name - ...

(QB_NEW_EN)


[grammar] ~104-~104: There might be a mistake here.
Context: ...AS: Replace with your chosen alias name - -storepass / -keypass: Replace with your chosen passwords - ...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ...ass: Replace with your chosen passwords - -dname`: Replace with your organization informa...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ... ``` Store this information securely: - Add to your password manager - Write in ...

(QB_NEW_EN)


[grammar] ~186-~186: There might be a mistake here.
Context: ...ad-keystore.jks ``` File explanation: - storePassword: The password that protects your keysto...

(QB_NEW_EN)


[grammar] ~187-~187: There might be a mistake here.
Context: ...assword that protects your keystore file - keyPassword: The password that protects your signin...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...assword that protects your signing key (often same as store password) - keyAlias: T...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...t protects your signing key (often same as store password) - keyAlias: The name ...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...gning key (often same as store password) - keyAlias: The name of your signing key within th...

(QB_NEW_EN)


[grammar] ~189-~189: There might be a mistake here.
Context: ... of your signing key within the keystore - storeFile: The filename of your keystore (should ...

(QB_NEW_EN)


[grammar] ~210-~210: There might be a mistake here.
Context: ...ASSWORD ``` Replace the placeholders: - YOUR_KEY_ALIAS: Use the value from your `key.propertie...

(QB_NEW_EN)


[grammar] ~230-~230: There might be a mistake here.
Context: ... GitHub builds use the same certificate. ### Common Errors and Solutions #### ❌ "keys...

(QB_NEW_EN)


[grammar] ~235-~235: There might be a mistake here.
Context: ...ur store password is wrong. Check these: 1. Verify the password in key.properties ...

(QB_NEW_EN)


[grammar] ~241-~241: There might be a mistake here.
Context: ...ist" Your alias name is wrong. Fix this: 1. List all aliases in your keystore: ...

(QB_NEW_EN)


[grammar] ~250-~250: There might be a mistake here.
Context: ... file not found" The file path is wrong: 1. Check the file exists: `ls -la app/upl...

(QB_NEW_EN)


[grammar] ~251-~251: There might be a mistake here.
Context: ...is wrong: 1. Check the file exists: ls -la app/upload-keystore.jks 2. Look for keystore files: `find . -name...

(QB_NEW_EN)


[grammar] ~252-~252: There might be a mistake here.
Context: ...re.jks2. **Look for keystore files**:find . -name ".jks" -o -name ".keystore"` 3. Update the storeFile path in key.prope...

(QB_NEW_EN)


[grammar] ~282-~282: There might be a mistake here.
Context: ... values** (you'll need them for GitHub): 1. Store Password: storePassword= value...

(QB_NEW_EN)


[grammar] ~283-~283: There might be a mistake here.
Context: ...Store Password**: storePassword= value 2. Key Password: keyPassword= value 3...

(QB_NEW_EN)


[grammar] ~284-~284: There might be a mistake here.
Context: .... Key Password: keyPassword= value 3. Key Alias: keyAlias= value 4. **Stor...

(QB_NEW_EN)


[grammar] ~285-~285: There might be a mistake here.
Context: ...ue 3. Key Alias: keyAlias= value 4. Store File: storeFile= value (usuall...

(QB_NEW_EN)


[grammar] ~335-~335: There might be a mistake here.
Context: ...secret**: #### 1. ANDROID_KEYSTORE_FILE - Name: ANDROID_KEYSTORE_FILE - **Valu...

(QB_NEW_EN)


[grammar] ~336-~336: There might be a mistake here.
Context: ...## 1. ANDROID_KEYSTORE_FILE - Name: ANDROID_KEYSTORE_FILE - Value: Your very long base64 string fr...

(QB_NEW_EN)


[grammar] ~339-~339: There might be a mistake here.
Context: ...tep 2 #### 2. ANDROID_KEYSTORE_PASSWORD - Name: ANDROID_KEYSTORE_PASSWORD - **...

(QB_NEW_EN)


[grammar] ~340-~340: There might be a mistake here.
Context: .... ANDROID_KEYSTORE_PASSWORD - Name: ANDROID_KEYSTORE_PASSWORD - Value: The storePassword value from ...

(QB_NEW_EN)


[grammar] ~343-~343: There might be a mistake here.
Context: ...properties #### 3. ANDROID_KEY_PASSWORD - Name: ANDROID_KEY_PASSWORD - **Val...

(QB_NEW_EN)


[grammar] ~344-~344: There might be a mistake here.
Context: ...### 3. ANDROID_KEY_PASSWORD - Name: ANDROID_KEY_PASSWORD - Value: The keyPassword value from yo...

(QB_NEW_EN)


[grammar] ~347-~347: There might be a mistake here.
Context: ...ey.properties #### 4. ANDROID_KEY_ALIAS - Name: ANDROID_KEY_ALIAS - Value:...

(QB_NEW_EN)


[grammar] ~348-~348: There might be a mistake here.
Context: ... #### 4. ANDROID_KEY_ALIAS - Name: ANDROID_KEY_ALIAS - Value: The keyAlias value from your ...

(QB_NEW_EN)


[grammar] ~369-~369: There might be a mistake here.
Context: ...ions log: #### ✅ Setup Android Keystore Should show: ``` Setting environment var...

(QB_NEW_EN)


[grammar] ~379-~379: There might be a mistake here.
Context: ...pload-keystore.jks ``` #### ✅ Build APK Should complete without errors about key...

(QB_NEW_EN)


[grammar] ~382-~382: There might be a mistake here.
Context: ...e or signing. #### ✅ Verify APK Signing Should show: ``` ✅ APK built successfull...

(QB_NEW_EN)


[grammar] ~398-~398: There might be a mistake here.
Context: ...ints" ``` GitHub Actions certificate: - Download the APK artifact from GitHub Ac...

(QB_NEW_EN)


[grammar] ~414-~414: There might be a mistake here.
Context: ...keystore.jks ``` The build system will: - Use your keystore when available locally...

(QB_NEW_EN)


[grammar] ~416-~416: There might be a mistake here.
Context: ...le locally - Fall back to debug signing if keystore is missing - Show warnings abo...

(QB_NEW_EN)


[grammar] ~423-~423: There might be a mistake here.
Context: ...❌ "keystore not found" in GitHub Actions Cause: Base64 keystore secret is wrong...

(QB_NEW_EN)


[grammar] ~425-~425: There might be a mistake here.
Context: ...secret is wrong or missing Solution: 1. Regenerate base64: `base64 -w 0 android/...

(QB_NEW_EN)


[grammar] ~426-~426: There might be a mistake here.
Context: ...ng Solution: 1. Regenerate base64: base64 -w 0 android/app/upload-keystore.jks 2. Update ANDROID_KEYSTORE_FILE secret wi...

(QB_NEW_EN)


[grammar] ~429-~429: There might be a mistake here.
Context: ...### ❌ "Wrong password" in GitHub Actions Cause: Password secrets don't match yo...

(QB_NEW_EN)


[grammar] ~431-~431: There might be a mistake here.
Context: ... don't match your keystore Solution: 1. Double-check the values in your local `k...

(QB_NEW_EN)


[grammar] ~433-~433: There might be a mistake here.
Context: ...ey.properties` 2. Update GitHub secrets with exact same values 3. Ensure no extra sp...

(QB_NEW_EN)


[style] ~433-~433: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...operties` 2. Update GitHub secrets with exact same values 3. Ensure no extra spaces or cha...

(EN_WORDINESS_PREMIUM_EXACT_SAME)


[grammar] ~438-~438: There might be a mistake here.
Context: ...PK wasn't properly signed Solution: 1. Check that keystore file was created in ...

(QB_NEW_EN)


[grammar] ~439-~439: There might be a mistake here.
Context: ...properly signed Solution: 1. Check that keystore file was created in "Setup And...

(QB_NEW_EN)


[grammar] ~450-~450: There might be a mistake here.
Context: ...nt fingerprints between local and GitHub Cause: GitHub is using different keyst...

(QB_NEW_EN)


[grammar] ~452-~452: There might be a mistake here.
Context: ...nt keystore or credentials Solution: 1. Verify base64 keystore decodes to identi...

(QB_NEW_EN)


[grammar] ~453-~453: There might be a mistake here.
Context: ...on**: 1. Verify base64 keystore decodes to identical file 2. Check all GitHub secr...

(QB_NEW_EN)


[grammar] ~460-~460: There might be a mistake here.
Context: ...tices ### 🔐 Keep Credentials Secure - Never commit key.properties or keystore...

(QB_NEW_EN)


[grammar] ~461-~461: There might be a mistake here.
Context: ... or keystore files to your repository - Use GitHub Secrets for all sensitive valu...

(QB_NEW_EN)


[grammar] ~463-~463: There might be a mistake here.
Context: ...rets - Enable 2FA on your GitHub account ### 🛡️ Backup Your Keystore - Keep secure ba...

(QB_NEW_EN)


[grammar] ~466-~466: There might be a mistake here.
Context: ...account ### 🛡️ Backup Your Keystore - Keep secure backups of your keystore file...

(QB_NEW_EN)


[grammar] ~467-~467: There might be a mistake here.
Context: ... secure backups of your keystore file - Document your passwords in a password man...

(QB_NEW_EN)


[grammar] ~468-~468: There might be a mistake here.
Context: ...manager - Test your backups periodically ### ⚠️ If Compromised If you suspect your key...

(QB_NEW_EN)


[grammar] ~470-~470: There might be a mistake here.
Context: ...kups periodically ### ⚠️ If Compromised If you suspect your keystore or password...

(QB_NEW_EN)


[grammar] ~471-~471: There might be a mistake here.
Context: ...r keystore or passwords are compromised: 1. Immediately change GitHub secrets 2. Gen...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
GITHUB_SECRETS_SETUP.md

128-128: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


217-217: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


371-371: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


384-384: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 YAMLlint (1.37.1)
.github/workflows/main.yml

[error] 77-77: trailing spaces

(trailing-spaces)


[error] 80-80: trailing spaces

(trailing-spaces)


[error] 171-171: no new line character at the end of file

(new-line-at-end-of-file)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
.gitignore (1)

39-41: Good: explicitly ignoring key.properties and CI keystore artifact

Explicit paths for android/key.properties and android/app/upload-keystore.jks are correct and align with the new signing flow. This reduces accidental commits of secrets.

.github/workflows/main.yml (1)

115-123: Env block not used by Gradle; keep for future parity but rely on key.properties or env fallback

The env block doesn’t affect Flutter/Gradle unless you read env vars in build.gradle. With the proposed Gradle fallback or key.properties write, this is fine. No change required, just calling it out.

Would you like me to open a follow-up PR to adopt only the env-based signing and drop the key.properties write in CI?

Comment thread .github/workflows/main.yml
Comment thread .github/workflows/main.yml
Comment thread android/app/build.gradle
Comment thread android/app/build.gradle
Comment thread GITHUB_SECRETS_SETUP.md
grunch and others added 4 commits August 25, 2025 11:12
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…ly fall back to debug

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
.github/workflows/main.yml (2)

140-158: Good move to real signature verification. Consider asserting the expected alias/cert hash.

jarsigner + apksigner is the right approach. For stronger guarantees, optionally fail if the cert subject/sha256 digest doesn’t match the expected production key.

You can extend the step like this:

             echo "Verifying with jarsigner..."
             jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk | sed -n '1,120p'
             # Try apksigner if available to print certs/fingerprint
             APKSIGNER=$(find "$ANDROID_HOME"/build-tools -name apksigner -type f | sort -V | tail -1 || true)
             if [ -n "$APKSIGNER" ]; then
               echo "Verifying with apksigner..."
-              "$APKSIGNER" verify --print-certs build/app/outputs/flutter-apk/app-release.apk
+              "$APKSIGNER" verify --print-certs build/app/outputs/flutter-apk/app-release.apk
+              # Optional strict check: ensure cert digest matches expected
+              if [ -n "${{ secrets.ANDROID_SIGNING_CERT_SHA256 }}" ]; then
+                "$APKSIGNER" verify --print-certs build/app/outputs/flutter-apk/app-release.apk | \
+                  awk '/Signer #1 certificate SHA-256 digest:/ {print $NF}' | \
+                  grep -Fq "${{ secrets.ANDROID_SIGNING_CERT_SHA256 }}" || {
+                    echo "❌ APK signed with unexpected certificate"; exit 1;
+                  }
+              fi
             fi

181-185: Also remove key.properties; add trailing newline (linters).

  • Clean up android/key.properties to avoid lingering secrets in the workspace.
  • Add a newline at EOF to satisfy linters.
       - name: Cleanup Keystore
         if: always() && steps.should_build.outputs.SHOULD_BUILD == 'true'
         run: |
           # Remove keystore file for security
-          rm -f android/app/upload-keystore.jks
+          rm -f android/app/upload-keystore.jks android/key.properties
+
android/app/build.gradle (4)

15-25: File-based detection looks good; ensure path convention is module-relative.

Given the workflow writes android/key.properties and the keystore at android/app/upload-keystore.jks, ensure key.properties uses storeFile=upload-keystore.jks (module-relative). If users put app/upload-keystore.jks, file(...) from the app module resolves to android/app/app/..., which will fail. Align docs/examples accordingly.


27-39: Trim env values and reject placeholders for robustness.

Minor hardening: trim env strings and optionally reject obvious placeholders or empty strings.

-    hasKeystoreFromEnv = req.every { System.getenv(it) }
+    hasKeystoreFromEnv = req.every { (System.getenv(it) ?: "").trim() }
     if (hasKeystoreFromEnv) {
         envKeystore = [
-            keyAlias     : System.getenv('ANDROID_KEY_ALIAS'),
-            keyPassword  : System.getenv('ANDROID_KEY_PASSWORD'),
-            storePassword: System.getenv('ANDROID_KEYSTORE_PASSWORD'),
-            storeFile    : System.getenv('ANDROID_KEYSTORE_FILE'),
+            keyAlias     : System.getenv('ANDROID_KEY_ALIAS')?.trim(),
+            keyPassword  : System.getenv('ANDROID_KEY_PASSWORD')?.trim(),
+            storePassword: System.getenv('ANDROID_KEYSTORE_PASSWORD')?.trim(),
+            storeFile    : System.getenv('ANDROID_KEYSTORE_FILE')?.trim(),
         ]
+        def invalids = ['REPLACE_ME_KEY_ALIAS','${ANDROID_KEY_ALIAS}'] as Set
+        hasKeystoreFromEnv = envKeystore.keyAlias && !invalids.contains(envKeystore.keyAlias)
     }

64-80: Signing config wiring is correct; double-check storeFile path semantics.

  • For file-based path you use file(keystoreProperties['storeFile']) which expects a module-relative path. This will work if key.properties uses storeFile=upload-keystore.jks (see workflow fix).
  • For env-based path, file(envKeystore.storeFile) likewise expects upload-keystore.jks.

If you prefer supporting rootProject-relative paths in key.properties (e.g., app/upload-keystore.jks), change only the file-based branch:

-                    storeFile    file(keystoreProperties['storeFile'])
+                    storeFile    rootProject.file(keystoreProperties['storeFile'])

Pick one convention and reflect it in documentation/sample.


94-100: Reasonable debug fallback; ensure releases aren’t published with it.

The warning helps. As a guard, you can fail the job if this is a tagged release and hasKeystore=false to prevent accidental publishing with a debug cert.

         release {
             if (hasKeystore) {
                 signingConfig signingConfigs.release
             } else {
                 // Fallback to debug signing for development
                 signingConfig signingConfigs.debug
                 logger.warn("Release build using debug signing - keystore not available")
+                if (System.getenv("GITHUB_REF")?.startsWith("refs/tags/")) {
+                    throw new GradleException("Refusing to build a tagged release without a release keystore")
+                }
             }
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c904432 and 4d07d7f.

📒 Files selected for processing (2)
  • .github/workflows/main.yml (3 hunks)
  • android/app/build.gradle (3 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/main.yml

86-86: could not parse as YAML: yaml: line 86: could not find expected ':'

(syntax-check)

🪛 YAMLlint (1.37.1)
.github/workflows/main.yml

[error] 87-87: syntax error: could not find expected ':'

(syntax)


[error] 185-185: no new line character at the end of file

(new-line-at-end-of-file)

🔇 Additional comments (3)
.github/workflows/main.yml (1)

125-139: Confirm release signing of the generated app bundle

The env block is correct and should remain using the module-relative path. These vars mirror GITHUB_ENV and provide Gradle with a fallback. Ensure ANDROID_KEYSTORE_FILE is still set to upload-keystore.jks to match your key.properties change above.

To validate that your app bundle is signed with the release keystore (rather than relying on an unset ANDROID_HOME), run this manually:

#!/bin/bash
set -euo pipefail

# Path to the built AAB
AAB="build/app/outputs/bundle/release/app-release.aab"

# Use the JDK’s jarsigner to verify the signature
JARSIGNER=$(which jarsigner)
if [[ -z "$JARSIGNER" ]]; then
  echo "jarsigner not found; ensure JDK is installed and on PATH"
  exit 1
fi

# Verify and print certificate info
"$JARSIGNER" -verify -verbose -certs "$AAB" | sed -n '1,120p'

You should see your keystore alias and certificate fingerprint in the output—confirm these match your release key.

android/app/build.gradle (2)

8-14: Solid scaffolding for dual-source signing; names read well.

The separation into hasKeystoreFromFile/env and envKeystore map is clear and maintainable.


41-41: LGTM.

Clear boolean for downstream blocks.

Comment on lines +72 to +83
- name: Setup Android Keystore
if: steps.should_build.outputs.SHOULD_BUILD == 'true'
run: |
# Create the keystore file from the secret
echo "${{ secrets.ANDROID_KEYSTORE_FILE }}" | base64 --decode > android/app/upload-keystore.jks

# Set environment variables for signing
echo "ANDROID_KEYSTORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
echo "ANDROID_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}" >> $GITHUB_ENV
echo "ANDROID_KEY_ALIAS=${{ secrets.ANDROID_KEY_ALIAS }}" >> $GITHUB_ENV
echo "ANDROID_KEYSTORE_FILE=upload-keystore.jks" >> $GITHUB_ENV

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid secrets appearing in command log; prefer shell vars over GitHub expressions.

Placing ${{ secrets.* }} inside the here-doc body means the literal secret values appear in the step’s echoed script. While GH masks them, using shell variables (export ...) keeps logs clean and reduces risk.

The diff in my previous comment switches to export + printf, which addresses this.

Comment thread .github/workflows/main.yml Outdated
replaces the heredoc syntax (cat > file <<EOF) with individual echo commands. This avoids the YAML parsing issue because the content is no longer treated as nested YAML structure. Each line is now a simple shell command that the YAML parser can handle correctly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
.github/workflows/main.yml (1)

72-88: Fix same-step env expansion; stop leaking secrets in echoed commands; write module-relative storeFile; harden file perms

  • ${{ env.ANDROID_KEYSTORE_FILE }} won’t be available within the same step that writes to $GITHUB_ENV, so storeFile expands to an empty string, breaking Gradle’s lookup. Use shell exports for immediate use.
  • Avoid embedding ${{ secrets.* }} directly in echo lines that appear in the step’s script; prefer shell vars to minimize the chance of secrets being revealed in logs.
  • storeFile should be upload-keystore.jks (module-relative), not app/...
  • Tighten permissions on the keystore and key.properties.

Apply:

       - name: Setup Android Keystore
         if: steps.should_build.outputs.SHOULD_BUILD == 'true'
         run: |
           # Create the keystore file from the secret
-          echo "${{ secrets.ANDROID_KEYSTORE_FILE }}" | base64 --decode > android/app/upload-keystore.jks
+          echo "${{ secrets.ANDROID_KEYSTORE_FILE }}" | base64 --decode > android/app/upload-keystore.jks
+          chmod 600 android/app/upload-keystore.jks
-
-          # Set environment variables for signing
-          echo "ANDROID_KEYSTORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
-          echo "ANDROID_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}" >> $GITHUB_ENV
-          echo "ANDROID_KEY_ALIAS=${{ secrets.ANDROID_KEY_ALIAS }}" >> $GITHUB_ENV
-          echo "ANDROID_KEYSTORE_FILE=upload-keystore.jks" >> $GITHUB_ENV
-
-          # Write key.properties so Gradle can pick it up without extra config
-          echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" > android/key.properties
-          echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
-          echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties
-          echo "storeFile=app/${{ env.ANDROID_KEYSTORE_FILE }}" >> android/key.properties
+          # Export for immediate use and persist for subsequent steps
+          export ANDROID_KEYSTORE_PASSWORD='${{ secrets.ANDROID_KEYSTORE_PASSWORD }}'
+          export ANDROID_KEY_PASSWORD='${{ secrets.ANDROID_KEY_PASSWORD }}'
+          export ANDROID_KEY_ALIAS='${{ secrets.ANDROID_KEY_ALIAS }}'
+          export ANDROID_KEYSTORE_FILE='upload-keystore.jks'
+          echo "ANDROID_KEYSTORE_PASSWORD=$ANDROID_KEYSTORE_PASSWORD" >> $GITHUB_ENV
+          echo "ANDROID_KEY_PASSWORD=$ANDROID_KEY_PASSWORD" >> $GITHUB_ENV
+          echo "ANDROID_KEY_ALIAS=$ANDROID_KEY_ALIAS" >> $GITHUB_ENV
+          echo "ANDROID_KEYSTORE_FILE=$ANDROID_KEYSTORE_FILE" >> $GITHUB_ENV
+
+          # Write key.properties so Gradle can pick it up (module-relative storeFile)
+          printf '%s\n' \
+            "storePassword=$ANDROID_KEYSTORE_PASSWORD" \
+            "keyPassword=$ANDROID_KEY_PASSWORD" \
+            "keyAlias=$ANDROID_KEY_ALIAS" \
+            "storeFile=upload-keystore.jks" \
+            > android/key.properties
+          chmod 600 android/key.properties
🧹 Nitpick comments (3)
.github/workflows/main.yml (3)

120-128: Avoid re-plumbing secrets here; prefer env propagated via $GITHUB_ENV or rely on key.properties

You already persist these values in the previous step and also write key.properties. To reduce surface area of secret usage and duplication, either:

  • reference env.* instead of secrets.* here, or
  • drop the env block entirely and rely on key.properties in Gradle.

Minimal change:

         env:
-          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
-          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
-          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
+          ANDROID_KEYSTORE_PASSWORD: ${{ env.ANDROID_KEYSTORE_PASSWORD }}
+          ANDROID_KEY_PASSWORD: ${{ env.ANDROID_KEY_PASSWORD }}
+          ANDROID_KEY_ALIAS: ${{ env.ANDROID_KEY_ALIAS }}
           ANDROID_KEYSTORE_FILE: upload-keystore.jks

129-136: Same as above: reference env. or drop block to rely on key.properties*

Keep a single source of truth for signing inputs to avoid drift.

         env:
-          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
-          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
-          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
+          ANDROID_KEYSTORE_PASSWORD: ${{ env.ANDROID_KEYSTORE_PASSWORD }}
+          ANDROID_KEY_PASSWORD: ${{ env.ANDROID_KEY_PASSWORD }}
+          ANDROID_KEY_ALIAS: ${{ env.ANDROID_KEY_ALIAS }}
           ANDROID_KEYSTORE_FILE: upload-keystore.jks

183-183: Add trailing newline to satisfy linters

Static analysis flags “no new line at end of file.” Add a newline at EOF.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4d07d7f and 620336c.

📒 Files selected for processing (1)
  • .github/workflows/main.yml (3 hunks)
🧰 Additional context used
🪛 YAMLlint (1.37.1)
.github/workflows/main.yml

[error] 183-183: no new line character at the end of file

(new-line-at-end-of-file)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

Comment thread .github/workflows/main.yml
Comment thread .github/workflows/main.yml Outdated
Provided comprehensive APK signature verification instructions
that align with the current CI implementation

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
GITHUB_SECRETS_SETUP.md (1)

382-447: Stronger signing verification: great improvement and aligned with prior feedback

You replaced the weak META-INF check with jarsigner/apksigner verification and fingerprint comparison. This addresses the earlier concern precisely and will prevent false positives from debug-signed builds. Looks good.

🧹 Nitpick comments (7)
GITHUB_SECRETS_SETUP.md (7)

397-407: Use proper headings and clarify jarsigner’s limitation (V1-only) — prefer apksigner for V2/V3/V4

Two tweaks for accuracy and linting:

  • Convert the bold “Step 1/Step 2” lines into real Markdown headings.
  • Explicitly note that jarsigner validates only V1 (JAR) signatures; modern Android APKs are validated fully by apksigner (V2/V3/V4). Keep jarsigner as a fallback.

Apply this diff:

-**Step 1: Verify signature integrity with jarsigner**
+##### Step 1: Verify signature integrity with jarsigner
@@
-jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk
+jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk
@@
-jarsigner -verify -verbose -certs -strict build/app/outputs/flutter-apk/app-release.apk
+jarsigner -verify -verbose -certs -strict build/app/outputs/flutter-apk/app-release.apk
+
+Note: jarsigner validates only V1 (JAR) signatures. Many Android builds use V2/V3/V4. Prefer apksigner for full Android signature verification when available.
@@
-**Step 2: Alternative verification with apksigner (if available)**
+##### Step 2: Verification with apksigner (preferred, if available)

Also applies to: 415-425


417-425: Broaden apksigner discovery to support ANDROID_SDK_ROOT and PATH

Right now you only search $ANDROID_HOME. Many setups export ANDROID_SDK_ROOT or have apksigner on PATH. Improve robustness.

Apply this diff:

-# Find apksigner in your Android SDK build-tools
-find $ANDROID_HOME/build-tools -name apksigner -type f | sort -V | tail -1
+# Find apksigner (prefer PATH; fallback to SDK env vars)
+APKSIGNER_BIN="$(command -v apksigner || true)"
+if [ -z "$APKSIGNER_BIN" ]; then
+  SDK_ROOT="${ANDROID_SDK_ROOT:-$ANDROID_HOME}"
+  APKSIGNER_BIN="$(find "$SDK_ROOT"/build-tools -name apksigner -type f 2>/dev/null | sort -V | tail -1 || true)"
+fi
+echo "${APKSIGNER_BIN:-apksigner not found}"
@@
-# Verify with apksigner
-apksigner verify --print-certs build/app/outputs/flutter-apk/app-release.apk
+# Verify with apksigner
+"$APKSIGNER_BIN" verify --print-certs build/app/outputs/flutter-apk/app-release.apk
@@
-apksigner verify --verbose build/app/outputs/flutter-apk/app-release.apk
+"$APKSIGNER_BIN" verify --verbose build/app/outputs/flutter-apk/app-release.apk

128-133: Add fenced code languages to satisfy markdownlint (MD040) and improve readability

Several code fences lack a language hint. Use text/properties to appease linters and highlight appropriately.

Apply this diff:

-```
+```text
 Keystore File: android/app/upload-keystore.jks
 Store Password: [the password you chose]
 Key Password: [the key password you chose]  
 Key Alias: [the alias you chose]

@@
- +text
Alias name: youraliasname
Creation date: Aug 13, 2025
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Your App Name, OU=Organization, O=Company...
Valid from: Wed Aug 13 18:47:47 UYT 2025 until: Sun Dec 29 18:47:47 UYT 2052
Certificate fingerprints:
SHA1: FD:DB:64:E4:7C:60:4D:BD:27:9F:A7:C2:D7:16:AB:6B:40:74:9A:F3
SHA256: 67:6F:67:26:76:D3:59:3A:C5:2F:01:7C:58:1C:50:C0:8B...

@@
-```
+```text
Setting environment variables
ANDROID_KEYSTORE_PASSWORD=***
ANDROID_KEY_PASSWORD=***
ANDROID_KEY_ALIAS=***
ANDROID_KEYSTORE_FILE=upload-keystore.jks

@@
- +text
✅ APK built successfully
Verifying with jarsigner...
jar verified.
✅ Certificate details displayed

@@
-```
+```text
jar verified.
Certificate details displayed...


Also applies to: 217-229, 371-377, 384-389, 411-413

---

`77-77`: **Typo: “Detal Amacuro” → “Delta Amacuro”**

Minor geographical correction.



Apply this diff:

```diff
-- **ST (State)**: Your state/province (e.g., `Detal Amacuro`)
+- **ST (State)**: Your state/province (e.g., `Delta Amacuro`)

324-353: Prefer Environment-scoped secrets with protections over repo-wide secrets

For production signing material, use GitHub Actions Environments with required reviewers, branch protections, and restricted access. This reduces blast radius compared to repository secrets.

Apply this diff:

 ### Navigate to GitHub Secrets
@@
-3. In the left sidebar, click **Secrets and variables**
-4. Click **Actions**
+3. In the left sidebar, click **Environments** (preferred for production) and create an environment like `release` with required reviewers and branch protections. Add your secrets under this environment.
+4. Alternatively, click **Secrets and variables** → **Actions** to add repository-level secrets (broader scope).

535-542: Add .gitignore guidance to prevent accidental commits of sensitive files

Ensure keystore and key.properties never get committed.

Apply this diff:

 ### 🔐 Keep Credentials Secure
 - Never commit `key.properties` or keystore files to your repository
 - Use GitHub Secrets for all sensitive values
 - Regularly audit who has access to repository secrets
 - Enable 2FA on your GitHub account
+
+Add these to your `.gitignore`:
+```gitignore
+# Android signing artifacts
+android/key.properties
+android/app/*.jks
+android/app/*.keystore
+android/app/*.p12
+```

543-552: Mention Play App Signing best practices and recovery path

Helpful context for teams using Google Play App Signing: treat this keystore as the “upload key,” and document recovery/reset via Play Console if compromised.

Apply this diff:

 ### 🛡️ Backup Your Keystore
 - Keep secure backups of your keystore file
 - Document your passwords in a password manager
 - Test your backups periodically
 
 ### ⚠️ If Compromised
 If you suspect your keystore or passwords are compromised:
 1. Immediately change GitHub secrets
 2. Generate new keystore for future releases
 3. Review GitHub Actions history for unauthorized builds
+4. If you use Google Play App Signing, rotate the upload key via Play Console and reconfigure CI with the new upload key. Document the new fingerprints.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 620336c and bc79c02.

📒 Files selected for processing (1)
  • GITHUB_SECRETS_SETUP.md (1 hunks)
🧰 Additional context used
🪛 LanguageTool
GITHUB_SECRETS_SETUP.md

[grammar] ~7-~7: There might be a mistake here.
Context: ...Guide Achieves By the end, you'll have: - ✅ Properly signed APKs built in GitHub A...

(QB_NEW_EN)


[grammar] ~8-~8: There might be a mistake here.
Context: ...erly signed APKs built in GitHub Actions - ✅ Consistent signing certificates across...

(QB_NEW_EN)


[grammar] ~9-~9: There might be a mistake here.
Context: ...t signing certificates across all builds - ✅ Secure credential storage in GitHub Se...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...ure credential storage in GitHub Secrets - ✅ APK installation that works without un...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...hat You Need Before starting, you need: - ✅ Android keystore file (.jks or `.key...

(QB_NEW_EN)


[grammar] ~50-~50: There might be a mistake here.
Context: ...-key.jks` If you find keystore files: - ✅ You have a keystore → Skip to "Cre...

(QB_NEW_EN)


[grammar] ~53-~53: There might be a mistake here.
Context: ...found** → These won't work for release builds, create a new one **If no keystore files...

(QB_NEW_EN)


[grammar] ~53-~53: There might be a mistake here.
Context: ...ork for release builds, create a new one If no keystore files found: - ❌ **No ke...

(QB_NEW_EN)


[grammar] ~55-~55: There might be a mistake here.
Context: ...a new one If no keystore files found: - ❌ No keystore exists → Continue to "...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...ds (choose strong, memorable passwords):** - Store Password: Protects the keystore ...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...ssword**: Protects the signing key (can be same as store password) Key Alias:...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...: Protects the signing key (can be same as store password) Key Alias: A name ...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ...tion (these identify your organization):** - CN (Common Name): Your app name or org...

(QB_NEW_EN)


[grammar] ~73-~73: There might be a mistake here.
Context: ...ganization (e.g., MyApp, My Company) - OU (Organizational Unit): Your departm...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ...g., Mobile Development, Engineering) - O (Organization): Your company name (e...

(QB_NEW_EN)


[grammar] ~75-~75: There might be a mistake here.
Context: ...our company name (e.g., MyCompany Inc) - L (Locality): Your city (e.g., `Tucupi...

(QB_NEW_EN)


[grammar] ~76-~76: There might be a mistake here.
Context: ...ocality)**: Your city (e.g., Tucupita) - ST (State): Your state/province (e.g.,...

(QB_NEW_EN)


[grammar] ~77-~77: There might be a mistake here.
Context: ...r state/province (e.g., Detal Amacuro) - C (Country): Your country code (e.g., ...

(QB_NEW_EN)


[grammar] ~100-~100: There might be a mistake here.
Context: ...OUR_COUNTRY" ``` Command Explanation: - -keystore upload-keystore.jks: Creates file named `upload-keystore.jk...

(QB_NEW_EN)


[grammar] ~101-~101: There might be a mistake here.
Context: ...pload-keystore.jks: Creates file named upload-keystore.jks--keyalg RSA -keysize 2048`: Uses RSA encryption with 2048-bit key ...

(QB_NEW_EN)


[grammar] ~102-~102: There might be a mistake here.
Context: ...SA encryption with 2048-bit key (secure) - -validity 10000: Certificate valid for ~27 years - `-al...

(QB_NEW_EN)


[grammar] ~103-~103: There might be a mistake here.
Context: ... 10000: Certificate valid for ~27 years - -alias YOUR_CHOSEN_ALIAS`: Replace with your chosen alias name - ...

(QB_NEW_EN)


[grammar] ~104-~104: There might be a mistake here.
Context: ...AS: Replace with your chosen alias name - -storepass / -keypass: Replace with your chosen passwords - ...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ...ass: Replace with your chosen passwords - -dname`: Replace with your organization informa...

(QB_NEW_EN)


[grammar] ~135-~135: There might be a mistake here.
Context: ... ``` Store this information securely: - Add to your password manager - Write in ...

(QB_NEW_EN)


[grammar] ~186-~186: There might be a mistake here.
Context: ...ad-keystore.jks ``` File explanation: - storePassword: The password that protects your keysto...

(QB_NEW_EN)


[grammar] ~187-~187: There might be a mistake here.
Context: ...assword that protects your keystore file - keyPassword: The password that protects your signin...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...assword that protects your signing key (often same as store password) - keyAlias: T...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...t protects your signing key (often same as store password) - keyAlias: The name ...

(QB_NEW_EN)


[grammar] ~188-~188: There might be a mistake here.
Context: ...gning key (often same as store password) - keyAlias: The name of your signing key within th...

(QB_NEW_EN)


[grammar] ~189-~189: There might be a mistake here.
Context: ... of your signing key within the keystore - storeFile: The filename of your keystore (should ...

(QB_NEW_EN)


[grammar] ~210-~210: There might be a mistake here.
Context: ...ASSWORD ``` Replace the placeholders: - YOUR_KEY_ALIAS: Use the value from your `key.propertie...

(QB_NEW_EN)


[grammar] ~230-~230: There might be a mistake here.
Context: ... GitHub builds use the same certificate. ### Common Errors and Solutions #### ❌ "keys...

(QB_NEW_EN)


[grammar] ~235-~235: There might be a mistake here.
Context: ...ur store password is wrong. Check these: 1. Verify the password in key.properties ...

(QB_NEW_EN)


[grammar] ~241-~241: There might be a mistake here.
Context: ...ist" Your alias name is wrong. Fix this: 1. List all aliases in your keystore: ...

(QB_NEW_EN)


[grammar] ~250-~250: There might be a mistake here.
Context: ... file not found" The file path is wrong: 1. Check the file exists: `ls -la app/upl...

(QB_NEW_EN)


[grammar] ~251-~251: There might be a mistake here.
Context: ...is wrong: 1. Check the file exists: ls -la app/upload-keystore.jks 2. Look for keystore files: `find . -name...

(QB_NEW_EN)


[grammar] ~252-~252: There might be a mistake here.
Context: ...re.jks2. **Look for keystore files**:find . -name ".jks" -o -name ".keystore"` 3. Update the storeFile path in key.prope...

(QB_NEW_EN)


[grammar] ~282-~282: There might be a mistake here.
Context: ... values** (you'll need them for GitHub): 1. Store Password: storePassword= value...

(QB_NEW_EN)


[grammar] ~283-~283: There might be a mistake here.
Context: ...Store Password**: storePassword= value 2. Key Password: keyPassword= value 3...

(QB_NEW_EN)


[grammar] ~284-~284: There might be a mistake here.
Context: .... Key Password: keyPassword= value 3. Key Alias: keyAlias= value 4. **Stor...

(QB_NEW_EN)


[grammar] ~285-~285: There might be a mistake here.
Context: ...ue 3. Key Alias: keyAlias= value 4. Store File: storeFile= value (usuall...

(QB_NEW_EN)


[grammar] ~335-~335: There might be a mistake here.
Context: ...secret**: #### 1. ANDROID_KEYSTORE_FILE - Name: ANDROID_KEYSTORE_FILE - **Valu...

(QB_NEW_EN)


[grammar] ~336-~336: There might be a mistake here.
Context: ...## 1. ANDROID_KEYSTORE_FILE - Name: ANDROID_KEYSTORE_FILE - Value: Your very long base64 string fr...

(QB_NEW_EN)


[grammar] ~339-~339: There might be a mistake here.
Context: ...tep 2 #### 2. ANDROID_KEYSTORE_PASSWORD - Name: ANDROID_KEYSTORE_PASSWORD - **...

(QB_NEW_EN)


[grammar] ~340-~340: There might be a mistake here.
Context: .... ANDROID_KEYSTORE_PASSWORD - Name: ANDROID_KEYSTORE_PASSWORD - Value: The storePassword value from ...

(QB_NEW_EN)


[grammar] ~343-~343: There might be a mistake here.
Context: ...properties #### 3. ANDROID_KEY_PASSWORD - Name: ANDROID_KEY_PASSWORD - **Val...

(QB_NEW_EN)


[grammar] ~344-~344: There might be a mistake here.
Context: ...### 3. ANDROID_KEY_PASSWORD - Name: ANDROID_KEY_PASSWORD - Value: The keyPassword value from yo...

(QB_NEW_EN)


[grammar] ~347-~347: There might be a mistake here.
Context: ...ey.properties #### 4. ANDROID_KEY_ALIAS - Name: ANDROID_KEY_ALIAS - Value:...

(QB_NEW_EN)


[grammar] ~348-~348: There might be a mistake here.
Context: ... #### 4. ANDROID_KEY_ALIAS - Name: ANDROID_KEY_ALIAS - Value: The keyAlias value from your ...

(QB_NEW_EN)


[grammar] ~369-~369: There might be a mistake here.
Context: ...ions log: #### ✅ Setup Android Keystore Should show: ``` Setting environment var...

(QB_NEW_EN)


[grammar] ~379-~379: There might be a mistake here.
Context: ...pload-keystore.jks ``` #### ✅ Build APK Should complete without errors about key...

(QB_NEW_EN)


[grammar] ~382-~382: There might be a mistake here.
Context: ...e or signing. #### ✅ Verify APK Signing Should show: ``` ✅ APK built successfull...

(QB_NEW_EN)


[grammar] ~443-~443: There might be a mistake here.
Context: ...rep "SHA256:" ``` Compare and verify: - Both keystore and APK should show identi...

(QB_NEW_EN)


[grammar] ~457-~457: There might be a mistake here.
Context: ...the build What the CI checks prevent: - Unsigned APKs reaching production - APKs...

(QB_NEW_EN)


[grammar] ~458-~458: There might be a mistake here.
Context: ...t:** - Unsigned APKs reaching production - APKs signed with debug certificates - AP...

(QB_NEW_EN)


[grammar] ~459-~459: There might be a mistake here.
Context: ...on - APKs signed with debug certificates - APKs signed with wrong/compromised certi...

(QB_NEW_EN)


[grammar] ~460-~460: There might be a mistake here.
Context: ...gned with wrong/compromised certificates - Signature corruption during build proces...

(QB_NEW_EN)


[grammar] ~461-~461: There might be a mistake here.
Context: ...sed certificates - Signature corruption during build process #### Troubleshooting Sig...

(QB_NEW_EN)


[grammar] ~466-~466: There might be a mistake here.
Context: ...gnature is invalid or corrupted - Check that keystore file exists and is not corrupt...

(QB_NEW_EN)


[grammar] ~471-~471: There might be a mistake here.
Context: ...gned with different certificate - Check that correct keystore file is being used - V...

(QB_NEW_EN)


[grammar] ~475-~475: There might be a mistake here.
Context: ...t found**: Keystore or APK access issues - Verify keystore file path and permission...

(QB_NEW_EN)


[grammar] ~492-~492: There might be a mistake here.
Context: ...keystore.jks ``` The build system will: - Use your keystore when available locally...

(QB_NEW_EN)


[grammar] ~494-~494: There might be a mistake here.
Context: ...le locally - Fall back to debug signing if keystore is missing - Show warnings abo...

(QB_NEW_EN)


[grammar] ~501-~501: There might be a mistake here.
Context: ...❌ "keystore not found" in GitHub Actions Cause: Base64 keystore secret is wrong...

(QB_NEW_EN)


[grammar] ~503-~503: There might be a mistake here.
Context: ...secret is wrong or missing Solution: 1. Regenerate base64: `base64 -w 0 android/...

(QB_NEW_EN)


[grammar] ~504-~504: There might be a mistake here.
Context: ...ng Solution: 1. Regenerate base64: base64 -w 0 android/app/upload-keystore.jks 2. Update ANDROID_KEYSTORE_FILE secret wi...

(QB_NEW_EN)


[grammar] ~507-~507: There might be a mistake here.
Context: ...### ❌ "Wrong password" in GitHub Actions Cause: Password secrets don't match yo...

(QB_NEW_EN)


[grammar] ~509-~509: There might be a mistake here.
Context: ... don't match your keystore Solution: 1. Double-check the values in your local `k...

(QB_NEW_EN)


[grammar] ~511-~511: There might be a mistake here.
Context: ...ey.properties` 2. Update GitHub secrets with exact same values 3. Ensure no extra sp...

(QB_NEW_EN)


[style] ~511-~511: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...operties` 2. Update GitHub secrets with exact same values 3. Ensure no extra spaces or cha...

(EN_WORDINESS_PREMIUM_EXACT_SAME)


[grammar] ~516-~516: There might be a mistake here.
Context: ...PK wasn't properly signed Solution: 1. Check that keystore file was created in ...

(QB_NEW_EN)


[grammar] ~517-~517: There might be a mistake here.
Context: ...properly signed Solution: 1. Check that keystore file was created in "Setup And...

(QB_NEW_EN)


[grammar] ~528-~528: There might be a mistake here.
Context: ...nt fingerprints between local and GitHub Cause: GitHub is using different keyst...

(QB_NEW_EN)


[grammar] ~530-~530: There might be a mistake here.
Context: ...nt keystore or credentials Solution: 1. Verify base64 keystore decodes to identi...

(QB_NEW_EN)


[grammar] ~538-~538: There might be a mistake here.
Context: ...tices ### 🔐 Keep Credentials Secure - Never commit key.properties or keystore...

(QB_NEW_EN)


[grammar] ~539-~539: There might be a mistake here.
Context: ... or keystore files to your repository - Use GitHub Secrets for all sensitive valu...

(QB_NEW_EN)


[grammar] ~541-~541: There might be a mistake here.
Context: ...rets - Enable 2FA on your GitHub account ### 🛡️ Backup Your Keystore - Keep secure ba...

(QB_NEW_EN)


[grammar] ~544-~544: There might be a mistake here.
Context: ...account ### 🛡️ Backup Your Keystore - Keep secure backups of your keystore file...

(QB_NEW_EN)


[grammar] ~545-~545: There might be a mistake here.
Context: ... secure backups of your keystore file - Document your passwords in a password man...

(QB_NEW_EN)


[grammar] ~546-~546: There might be a mistake here.
Context: ...manager - Test your backups periodically ### ⚠️ If Compromised If you suspect your key...

(QB_NEW_EN)


[grammar] ~548-~548: There might be a mistake here.
Context: ...kups periodically ### ⚠️ If Compromised If you suspect your keystore or password...

(QB_NEW_EN)


[grammar] ~549-~549: There might be a mistake here.
Context: ...r keystore or passwords are compromised: 1. Immediately change GitHub secrets 2. Gen...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
GITHUB_SECRETS_SETUP.md

128-128: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


217-217: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


371-371: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


384-384: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


397-397: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


410-410: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


415-415: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (1)
GITHUB_SECRETS_SETUP.md (1)

448-456: CI Workflow Signing and Cleanup Verified

All documented signing verification and cleanup steps are present and correctly enforced in .github/workflows/main.yml:

  • Keystore decoding and writing
    echo "${{ secrets.ANDROID_KEYSTORE_FILE }}" | base64 --decode > android/app/upload-keystore.jks (line 76)
    • Environment variables and key.properties are generated for Gradle (lines 80–88)
  • Signature verification with failure on non-zero exit codes
    set -e is enabled before running jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk (lines 144–146)
    apksigner verify --print-certs fallback is invoked if apksigner is found in build-tools (lines 147–149)
  • Cleanup of keystore artifacts
    rm -f android/app/upload-keystore.jks in the “Cleanup Keystore” step (line 183)
  • All steps properly gated by steps.should_build.outputs.SHOULD_BUILD == 'true', with cleanup using if: always() to ensure removal even on failure (lines 56–74, 180–182)

No further changes required.

Comment thread GITHUB_SECRETS_SETUP.md
Comment thread GITHUB_SECRETS_SETUP.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
.github/workflows/main.yml (1)

72-89: Fix env interpolation bug and wrong storeFile path in key.properties (leads to unsigned/mis-signed builds).

  • Using ${{ env.ANDROID_KEYSTORE_FILE }} inside the same run block won’t see values written to $GITHUB_ENV earlier in that block; it expands before the step runs, resulting in an empty value. That writes storeFile=app/ and breaks signing resolution.
  • For Gradle’s typical Flutter setup (key.properties at android/, file() resolved from app module), storeFile should be module-relative: upload-keystore.jks (not app/...).
  • Also prefer shell variables over inline ${{ secrets.* }} in echoes to keep logs cleaner.

Apply this refactor to make the step robust and self‑contained:

       - name: Setup Android Keystore
         if: steps.should_build.outputs.SHOULD_BUILD == 'true'
         run: |
           # Create the keystore file from the secret
           echo "${{ secrets.ANDROID_KEYSTORE_FILE }}" | base64 --decode > android/app/upload-keystore.jks

-          # Set environment variables for signing
-          echo "ANDROID_KEYSTORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> $GITHUB_ENV
-          echo "ANDROID_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}" >> $GITHUB_ENV
-          echo "ANDROID_KEY_ALIAS=${{ secrets.ANDROID_KEY_ALIAS }}" >> $GITHUB_ENV
-          echo "ANDROID_KEYSTORE_FILE=upload-keystore.jks" >> $GITHUB_ENV
-
-          # Write key.properties so Gradle can pick it up without extra config
-          echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" > android/key.properties
-          echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/key.properties
-          echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/key.properties
-          echo "storeFile=app/${{ env.ANDROID_KEYSTORE_FILE }}" >> android/key.properties
+          # Export for immediate use in this step and persist for later steps
+          export ANDROID_KEYSTORE_PASSWORD='${{ secrets.ANDROID_KEYSTORE_PASSWORD }}'
+          export ANDROID_KEY_PASSWORD='${{ secrets.ANDROID_KEY_PASSWORD }}'
+          export ANDROID_KEY_ALIAS='${{ secrets.ANDROID_KEY_ALIAS }}'
+          export ANDROID_KEYSTORE_FILE='upload-keystore.jks'
+          {
+            echo "ANDROID_KEYSTORE_PASSWORD=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}"
+            echo "ANDROID_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}"
+            echo "ANDROID_KEY_ALIAS=${{ secrets.ANDROID_KEY_ALIAS }}"
+            echo "ANDROID_KEYSTORE_FILE=upload-keystore.jks"
+          } >> "$GITHUB_ENV"
+
+          # Write key.properties (module-relative storeFile) without echoing secrets in logs
+          printf '%s\n' \
+            "storePassword=$ANDROID_KEYSTORE_PASSWORD" \
+            "keyPassword=$ANDROID_KEY_PASSWORD" \
+            "keyAlias=$ANDROID_KEY_ALIAS" \
+            "storeFile=upload-keystore.jks" \
+            > android/key.properties
+
+          # Restrict permissions
+          chmod 600 android/app/upload-keystore.jks android/key.properties
🧹 Nitpick comments (8)
.github/workflows/main.yml (3)

123-128: Optional: drop per-step env blocks; rely on $GITHUB_ENV to reduce duplication.

You already persist these vars via $GITHUB_ENV in the setup step. The extra env sections here duplicate secrets injection and increase surface area in logs. Consider removing them for simplicity.

       - name: Build APK
         if: steps.should_build.outputs.SHOULD_BUILD == 'true'
-        run: flutter build apk --release --dart-define=APP_VERSION=${{ env.VERSION }} --dart-define=GIT_COMMIT=${{ github.sha }}
-        env:
-          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
-          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
-          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
-          ANDROID_KEYSTORE_FILE: upload-keystore.jks
+        run: flutter build apk --release --dart-define=APP_VERSION=${{ env.VERSION }} --dart-define=GIT_COMMIT=${{ github.sha }}
@@
       - name: Build appBundle
         if: steps.should_build.outputs.SHOULD_BUILD == 'true'
-        run: flutter build appbundle --release --dart-define=APP_VERSION=${{ env.VERSION }} --dart-define=GIT_COMMIT=${{ github.sha }}
-        env:
-          ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
-          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
-          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
-          ANDROID_KEYSTORE_FILE: upload-keystore.jks
+        run: flutter build appbundle --release --dart-define=APP_VERSION=${{ env.VERSION }} --dart-define=GIT_COMMIT=${{ github.sha }}

Also applies to: 132-136


138-169: Normalize fingerprint formats before comparison; handle separators and casing.

apksigner prints SHA‑256 with colons/spaces; teams often store the secret with or without separators and in mixed case. Normalize both sides for reliable matching.

               # Optional: enforce signer fingerprint if provided
               if [ -n "${{ secrets.ANDROID_CERT_SHA256 }}" ]; then
-                FPR=$("$APKSIGNER" verify --print-certs "$APK" 2>/dev/null \
-                  | awk -F': ' '/SHA-256 digest/{print $2}' | tr -d ' ')
-                if [ "$FPR" != "${{ secrets.ANDROID_CERT_SHA256 }}" ]; then
+                FPR=$("$APKSIGNER" verify --print-certs "$APK" 2>/dev/null \
+                  | awk -F': ' '/SHA-256 digest/{print $2}' \
+                  | tr -d ' :\n' \
+                  | tr '[:lower:]' '[:upper:]')
+                EXPECTED=$(printf '%s' '${{ secrets.ANDROID_CERT_SHA256 }}' \
+                  | tr -d ' :\n' \
+                  | tr '[:lower:]' '[:upper:]')
+                if [ "$FPR" != "$EXPECTED" ]; then
                   echo "❌ APK cert SHA-256 mismatch"
                   exit 1
                 fi
               fi

If you want this to run even when apksigner is missing, we can add a fallback that parses jarsigner output (less ideal for V2/V3) or compare against the keystore via keytool while it’s still on disk. Happy to wire that if needed.


196-196: Add missing newline at EOF to satisfy linters.

YAMLlint flagged “no new line character at the end of file”. Add a trailing newline.

GITHUB_SECRETS_SETUP.md (5)

126-139: Mark fenced block language; tighten “secure storage” guidance.

  • Add a language hint to satisfy markdownlint (MD040). “text” works here.
  • The storage guidance looks good; thanks for removing the email suggestion.
 **CRITICAL**: Immediately write down these values - you'll need them forever:
-```
+```text
 Keystore File: android/app/upload-keystore.jks
 Store Password: [the password you chose]
 Key Password: [the key password you chose]  
 Key Alias: [the alias you chose]

---

`292-303`: **Nice cross‑platform base64 guidance. Add a brief “don’t paste into logs” caution.**

Minor doc enhancement: warn readers not to paste the long base64 string into issues/PRs or build logs.



```diff
 Convert your keystore file to base64 for secure storage in GitHub:
@@
-**Copy the entire output** - it will be thousands of characters long. Save it temporarily in a text file.
+**Copy the entire output** — it will be thousands of characters long. Save it temporarily in a local text file.
+Do not paste this value into public issues, PRs, or CI logs.

Also applies to: 311-328


214-229: Add language hint for “Expected Success Output” block.

Satisfies MD040 and improves readability.

-If successful, you'll see something like:
-```
+If successful, you'll see something like:
+```text
 Alias name: youraliasname
 Creation date: Aug 13, 2025
 Entry type: PrivateKeyEntry
 Certificate chain length: 1
 Certificate[1]:
 Owner: CN=Your App Name, OU=Organization, O=Company...
 Valid from: Wed Aug 13 18:47:47 UYT 2025 until: Sun Dec 29 18:47:47 UYT 2052
 Certificate fingerprints:
      SHA1: FD:DB:64:E4:7C:60:4D:BD:27:9F:A7:C2:D7:16:AB:6B:40:74:9A:F3
      SHA256: 67:6F:67:26:76:D3:59:3A:C5:2F:01:7C:58:1C:50:C0:8B...

---

`379-387`: **Mark these output blocks with a language to satisfy MD040.**

Use “text” to avoid syntax highlighting; keeps CI log snippets clean.



```diff
 #### ✅ Setup Android Keystore
 Should show:
-```
+```text
 Setting environment variables
 ANDROID_KEYSTORE_PASSWORD=***
 ANDROID_KEY_PASSWORD=***
 ANDROID_KEY_ALIAS=***
 ANDROID_KEYSTORE_FILE=upload-keystore.jks

@@

✅ Verify APK Signing

Should show:
- +text
✅ APK built successfully
Verifying with jarsigner...
jar verified.
✅ Certificate details displayed

Also applies to: 392-400


443-456: Normalize fingerprints when comparing; note colon/casing differences.

Readers often copy SHA‑256 with/without colons. Suggest normalizing both sides in the examples so comparisons don’t fail on formatting.

 **Extract keystore certificate SHA-256 fingerprint:**
 ```bash
 keytool -list -v -keystore android/app/upload-keystore.jks -alias YOUR_ALIAS -storepass YOUR_STORE_PASSWORD | grep "SHA256:"

Extract APK certificate fingerprint:

-# Method 1: Using jarsigner
-jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk | grep "SHA256:"
+# Method 1: Using jarsigner (normalize by stripping colons/spaces)
+jarsigner -verify -verbose -certs build/app/outputs/flutter-apk/app-release.apk \
+  | awk -F': ' '/SHA256/{print $2}' | tr -d ' :\n' | tr '[:lower:]' '[:upper:]'

# Method 2: Using apksigner (if available)
-apksigner verify --print-certs build/app/outputs/flutter-apk/app-release.apk | grep "SHA256:"
+apksigner verify --print-certs build/app/outputs/flutter-apk/app-release.apk \
+  | awk -F': ' '/SHA-256 digest/{print $2}' | tr -d ' :\n' | tr '[:lower:]' '[:upper:]'

Compare and verify:
-- Both keystore and APK should show identical SHA-256 fingerprints
+- Both keystore and APK should show identical SHA-256 fingerprints (ignore colons/case)


</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

**💡 Knowledge Base configuration:**

- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between bc79c027bfad837877e56f9cb5121d7baa9ed157 and d14f9f6a732b56044ddf9974e1dcd871dc115772.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `.github/workflows/main.yml` (3 hunks)
* `GITHUB_SECRETS_SETUP.md` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>GITHUB_SECRETS_SETUP.md</summary>

[grammar] ~7-~7: There might be a mistake here.
Context: ...Guide Achieves  By the end, you'll have: - ✅ Properly signed APKs built in GitHub A...

(QB_NEW_EN)

---

[grammar] ~8-~8: There might be a mistake here.
Context: ...erly signed APKs built in GitHub Actions - ✅ Consistent signing certificates across...

(QB_NEW_EN)

---

[grammar] ~9-~9: There might be a mistake here.
Context: ...t signing certificates across all builds - ✅ Secure credential storage in GitHub Se...

(QB_NEW_EN)

---

[grammar] ~10-~10: There might be a mistake here.
Context: ...ure credential storage in GitHub Secrets - ✅ APK installation that works without un...

(QB_NEW_EN)

---

[grammar] ~21-~21: There might be a mistake here.
Context: ...hat You Need  Before starting, you need: - ✅ Android keystore file (`.jks` or `.key...

(QB_NEW_EN)

---

[grammar] ~50-~50: There might be a mistake here.
Context: ...-key.jks`  **If you find keystore files:** - ✅ **You have a keystore** → Skip to "Cre...

(QB_NEW_EN)

---

[grammar] ~53-~53: There might be a mistake here.
Context: ...found** → These won't work for release builds, create a new one  **If no keystore files...

(QB_NEW_EN)

---

[grammar] ~53-~53: There might be a mistake here.
Context: ...ork for release builds, create a new one  **If no keystore files found:** - ❌ **No ke...

(QB_NEW_EN)

---

[grammar] ~55-~55: There might be a mistake here.
Context: ...a new one  **If no keystore files found:** - ❌ **No keystore exists** → Continue to "...

(QB_NEW_EN)

---

[grammar] ~66-~66: There might be a mistake here.
Context: ...ds (choose strong, memorable passwords):** - **Store Password**: Protects the keystore ...

(QB_NEW_EN)

---

[grammar] ~68-~68: There might be a mistake here.
Context: ...ssword**: Protects the signing key (can be same as store password)  **Key Alias**:...

(QB_NEW_EN)

---

[grammar] ~68-~68: There might be a mistake here.
Context: ...: Protects the signing key (can be same as store password)  **Key Alias**: A name ...

(QB_NEW_EN)

---

[grammar] ~72-~72: There might be a mistake here.
Context: ...tion (these identify your organization):** - **CN (Common Name)**: Your app name or org...

(QB_NEW_EN)

---

[grammar] ~73-~73: There might be a mistake here.
Context: ...ganization (e.g., `MyApp`, `My Company`) - **OU (Organizational Unit)**: Your departm...

(QB_NEW_EN)

---

[grammar] ~74-~74: There might be a mistake here.
Context: ...g., `Mobile Development`, `Engineering`) - **O (Organization)**: Your company name (e...

(QB_NEW_EN)

---

[grammar] ~75-~75: There might be a mistake here.
Context: ...our company name (e.g., `MyCompany Inc`) - **L (Locality)**: Your city (e.g., `Tucupi...

(QB_NEW_EN)

---

[grammar] ~76-~76: There might be a mistake here.
Context: ...ocality)**: Your city (e.g., `Tucupita`) - **ST (State)**: Your state/province (e.g.,...

(QB_NEW_EN)

---

[grammar] ~77-~77: There might be a mistake here.
Context: ...r state/province (e.g., `Delta Amacuro`) - **C (Country)**: Your country code (e.g., ...

(QB_NEW_EN)

---

[grammar] ~100-~100: There might be a mistake here.
Context: ...OUR_COUNTRY" ```  **Command Explanation:** - `-keystore upload-keystore.jks`: Creates file named `upload-keystore.jk...

(QB_NEW_EN)

---

[grammar] ~101-~101: There might be a mistake here.
Context: ...pload-keystore.jks`: Creates file named `upload-keystore.jks` - `-keyalg RSA -keysize 2048`: Uses RSA encryption with 2048-bit key ...

(QB_NEW_EN)

---

[grammar] ~102-~102: There might be a mistake here.
Context: ...SA encryption with 2048-bit key (secure) - `-validity 10000`: Certificate valid for ~27 years - `-al...

(QB_NEW_EN)

---

[grammar] ~103-~103: There might be a mistake here.
Context: ... 10000`: Certificate valid for ~27 years - `-alias YOUR_CHOSEN_ALIAS`: Replace with your chosen alias name - ...

(QB_NEW_EN)

---

[grammar] ~104-~104: There might be a mistake here.
Context: ...AS`: Replace with your chosen alias name - `-storepass / -keypass`: Replace with your chosen passwords - `...

(QB_NEW_EN)

---

[grammar] ~105-~105: There might be a mistake here.
Context: ...ass`: Replace with your chosen passwords - `-dname`: Replace with your organization informa...

(QB_NEW_EN)

---

[grammar] ~135-~135: There might be a mistake here.
Context: ... ```  **Store this information securely:** - Add to your password manager - Store in ...

(QB_NEW_EN)

---

[grammar] ~186-~186: There might be a mistake here.
Context: ...ad-keystore.jks ```  **File explanation:** - `storePassword`: The password that protects your keysto...

(QB_NEW_EN)

---

[grammar] ~187-~187: There might be a mistake here.
Context: ...assword that protects your keystore file - `keyPassword`: The password that protects your signin...

(QB_NEW_EN)

---

[grammar] ~188-~188: There might be a mistake here.
Context: ...assword that protects your signing key (often same as store password) - `keyAlias`: T...

(QB_NEW_EN)

---

[grammar] ~188-~188: There might be a mistake here.
Context: ...t protects your signing key (often same as store password) - `keyAlias`: The name ...

(QB_NEW_EN)

---

[grammar] ~188-~188: There might be a mistake here.
Context: ...gning key (often same as store password) - `keyAlias`: The name of your signing key within th...

(QB_NEW_EN)

---

[grammar] ~189-~189: There might be a mistake here.
Context: ... of your signing key within the keystore - `storeFile`: The filename of your keystore (should ...

(QB_NEW_EN)

---

[grammar] ~210-~210: There might be a mistake here.
Context: ...ASSWORD ```  **Replace the placeholders:** - `YOUR_KEY_ALIAS`: Use the value from your `key.propertie...

(QB_NEW_EN)

---

[grammar] ~230-~230: There might be a mistake here.
Context: ... GitHub builds use the same certificate.  ### Common Errors and Solutions  #### ❌ "keys...

(QB_NEW_EN)

---

[grammar] ~235-~235: There might be a mistake here.
Context: ...ur store password is wrong. Check these: 1. **Verify the password in key.properties** ...

(QB_NEW_EN)

---

[grammar] ~241-~241: There might be a mistake here.
Context: ...ist" Your alias name is wrong. Fix this: 1. **List all aliases in your keystore**:    ...

(QB_NEW_EN)

---

[grammar] ~250-~250: There might be a mistake here.
Context: ... file not found" The file path is wrong: 1. **Check the file exists**: `ls -la app/upl...

(QB_NEW_EN)

---

[grammar] ~251-~251: There might be a mistake here.
Context: ...is wrong: 1. **Check the file exists**: `ls -la app/upload-keystore.jks` 2. **Look for keystore files**: `find . -name...

(QB_NEW_EN)

---

[grammar] ~252-~252: There might be a mistake here.
Context: ...re.jks` 2. **Look for keystore files**: `find . -name "*.jks" -o -name "*.keystore"` 3. **Update the storeFile path** in key.prope...

(QB_NEW_EN)

---

[grammar] ~282-~282: There might be a mistake here.
Context: ... values** (you'll need them for GitHub): 1. **Store Password**: `storePassword=` value...

(QB_NEW_EN)

---

[grammar] ~283-~283: There might be a mistake here.
Context: ...Store Password**: `storePassword=` value 2. **Key Password**: `keyPassword=` value   3...

(QB_NEW_EN)

---

[grammar] ~284-~284: There might be a mistake here.
Context: .... **Key Password**: `keyPassword=` value   3. **Key Alias**: `keyAlias=` value 4. **Stor...

(QB_NEW_EN)

---

[grammar] ~285-~285: There might be a mistake here.
Context: ...ue   3. **Key Alias**: `keyAlias=` value 4. **Store File**: `storeFile=` value (usuall...

(QB_NEW_EN)

---

[grammar] ~345-~345: There might be a mistake here.
Context: ...secret**:  #### 1. ANDROID_KEYSTORE_FILE - **Name**: `ANDROID_KEYSTORE_FILE` - **Valu...

(QB_NEW_EN)

---

[grammar] ~346-~346: There might be a mistake here.
Context: ...## 1. ANDROID_KEYSTORE_FILE - **Name**: `ANDROID_KEYSTORE_FILE` - **Value**: Your very long base64 string fr...

(QB_NEW_EN)

---

[grammar] ~349-~349: There might be a mistake here.
Context: ...tep 2  #### 2. ANDROID_KEYSTORE_PASSWORD - **Name**: `ANDROID_KEYSTORE_PASSWORD` - **...

(QB_NEW_EN)

---

[grammar] ~350-~350: There might be a mistake here.
Context: .... ANDROID_KEYSTORE_PASSWORD - **Name**: `ANDROID_KEYSTORE_PASSWORD` - **Value**: The `storePassword` value from ...

(QB_NEW_EN)

---

[grammar] ~353-~353: There might be a mistake here.
Context: ...properties  #### 3. ANDROID_KEY_PASSWORD - **Name**: `ANDROID_KEY_PASSWORD`   - **Val...

(QB_NEW_EN)

---

[grammar] ~354-~354: There might be a mistake here.
Context: ...### 3. ANDROID_KEY_PASSWORD - **Name**: `ANDROID_KEY_PASSWORD`   - **Value**: The `keyPassword` value from yo...

(QB_NEW_EN)

---

[grammar] ~357-~357: There might be a mistake here.
Context: ...ey.properties  #### 4. ANDROID_KEY_ALIAS - **Name**: `ANDROID_KEY_ALIAS` - **Value**:...

(QB_NEW_EN)

---

[grammar] ~358-~358: There might be a mistake here.
Context: ...  #### 4. ANDROID_KEY_ALIAS - **Name**: `ANDROID_KEY_ALIAS` - **Value**: The `keyAlias` value from your ...

(QB_NEW_EN)

---

[grammar] ~379-~379: There might be a mistake here.
Context: ...ions log:  #### ✅ Setup Android Keystore Should show: ``` Setting environment var...

(QB_NEW_EN)

---

[grammar] ~389-~389: There might be a mistake here.
Context: ...pload-keystore.jks ```  #### ✅ Build APK Should complete without errors about key...

(QB_NEW_EN)

---

[grammar] ~392-~392: There might be a mistake here.
Context: ...e or signing.  #### ✅ Verify APK Signing   Should show: ``` ✅ APK built successfull...

(QB_NEW_EN)

---

[grammar] ~457-~457: There might be a mistake here.
Context: ...rep "SHA256:" ```  **Compare and verify:** - Both keystore and APK should show identi...

(QB_NEW_EN)

---

[grammar] ~471-~471: There might be a mistake here.
Context: ...the build  **What the CI checks prevent:** - Unsigned APKs reaching production - APKs...

(QB_NEW_EN)

---

[grammar] ~472-~472: There might be a mistake here.
Context: ...t:** - Unsigned APKs reaching production - APKs signed with debug certificates - AP...

(QB_NEW_EN)

---

[grammar] ~473-~473: There might be a mistake here.
Context: ...on - APKs signed with debug certificates - APKs signed with wrong/compromised certi...

(QB_NEW_EN)

---

[grammar] ~474-~474: There might be a mistake here.
Context: ...gned with wrong/compromised certificates - Signature corruption during build proces...

(QB_NEW_EN)

---

[grammar] ~475-~475: There might be a mistake here.
Context: ...sed certificates - Signature corruption during build process  #### Troubleshooting Sig...

(QB_NEW_EN)

---

[grammar] ~480-~480: There might be a mistake here.
Context: ...gnature is invalid or corrupted - Check that keystore file exists and is not corrupt...

(QB_NEW_EN)

---

[grammar] ~485-~485: There might be a mistake here.
Context: ...gned with different certificate - Check that correct keystore file is being used - V...

(QB_NEW_EN)

---

[grammar] ~489-~489: There might be a mistake here.
Context: ...t found**: Keystore or APK access issues - Verify keystore file path and permission...

(QB_NEW_EN)

---

[grammar] ~506-~506: There might be a mistake here.
Context: ...keystore.jks ```  The build system will: - Use your keystore when available locally...

(QB_NEW_EN)

---

[grammar] ~508-~508: There might be a mistake here.
Context: ...le locally - Fall back to debug signing if keystore is missing - Show warnings abo...

(QB_NEW_EN)

---

[grammar] ~515-~515: There might be a mistake here.
Context: ...❌ "keystore not found" in GitHub Actions **Cause**: Base64 keystore secret is wrong...

(QB_NEW_EN)

---

[grammar] ~517-~517: There might be a mistake here.
Context: ...secret is wrong or missing **Solution**:  1. Regenerate base64: `base64 -w 0 android/...

(QB_NEW_EN)

---

[grammar] ~518-~518: There might be a mistake here.
Context: ...ng **Solution**:  1. Regenerate base64: `base64 -w 0 android/app/upload-keystore.jks` 2. Update `ANDROID_KEYSTORE_FILE` secret wi...

(QB_NEW_EN)

---

[grammar] ~521-~521: There might be a mistake here.
Context: ...### ❌ "Wrong password" in GitHub Actions   **Cause**: Password secrets don't match yo...

(QB_NEW_EN)

---

[grammar] ~523-~523: There might be a mistake here.
Context: ... don't match your keystore **Solution**: 1. Double-check the values in your local `k...

(QB_NEW_EN)

---

[grammar] ~525-~525: There might be a mistake here.
Context: ...ey.properties` 2. Update GitHub secrets with exact same values 3. Ensure no extra sp...

(QB_NEW_EN)

---

[style] ~525-~525: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...operties` 2. Update GitHub secrets with exact same values 3. Ensure no extra spaces or cha...

(EN_WORDINESS_PREMIUM_EXACT_SAME)

---

[grammar] ~530-~530: There might be a mistake here.
Context: ...PK wasn't properly signed  **Solution**: 1. Check that keystore file was created in ...

(QB_NEW_EN)

---

[grammar] ~531-~531: There might be a mistake here.
Context: ...properly signed  **Solution**: 1. Check that keystore file was created in "Setup And...

(QB_NEW_EN)

---

[grammar] ~542-~542: There might be a mistake here.
Context: ...nt fingerprints between local and GitHub **Cause**: GitHub is using different keyst...

(QB_NEW_EN)

---

[grammar] ~544-~544: There might be a mistake here.
Context: ...nt keystore or credentials **Solution**: 1. Verify base64 keystore decodes to identi...

(QB_NEW_EN)

---

[grammar] ~552-~552: There might be a mistake here.
Context: ...tices  ### 🔐 Keep Credentials Secure - Never commit `key.properties` or keystore...

(QB_NEW_EN)

---

[grammar] ~553-~553: There might be a mistake here.
Context: ... or keystore files to your repository - Use GitHub Secrets for all sensitive valu...

(QB_NEW_EN)

---

[grammar] ~555-~555: There might be a mistake here.
Context: ...rets - Enable 2FA on your GitHub account  ### 🛡️ Backup Your Keystore - Keep secure ba...

(QB_NEW_EN)

---

[grammar] ~558-~558: There might be a mistake here.
Context: ...account  ### 🛡️ Backup Your Keystore - Keep secure backups of your keystore file...

(QB_NEW_EN)

---

[grammar] ~559-~559: There might be a mistake here.
Context: ... secure backups of your keystore file - Document your passwords in a password man...

(QB_NEW_EN)

---

[grammar] ~560-~560: There might be a mistake here.
Context: ...manager - Test your backups periodically  ### ⚠️ If Compromised If you suspect your key...

(QB_NEW_EN)

---

[grammar] ~562-~562: There might be a mistake here.
Context: ...kups periodically  ### ⚠️ If Compromised If you suspect your keystore or password...

(QB_NEW_EN)

---

[grammar] ~563-~563: There might be a mistake here.
Context: ...r keystore or passwords are compromised: 1. Immediately change GitHub secrets 2. Gen...

(QB_NEW_EN)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>GITHUB_SECRETS_SETUP.md</summary>

128-128: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

217-217: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

381-381: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

394-394: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

423-423: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>
<details>
<summary>🪛 YAMLlint (1.37.1)</summary>

<details>
<summary>.github/workflows/main.yml</summary>

[error] 196-196: no new line character at the end of file

(new-line-at-end-of-file)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)</summary>

* GitHub Check: build

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>GITHUB_SECRETS_SETUP.md (1)</summary><blockquote>

`170-176`: **Good: key.properties examples use module-relative storeFile.**

This matches Gradle’s usual resolution from the app module. Keep it consistent with CI (ensure the workflow writes `storeFile=upload-keystore.jks`, not `app/...`).





Also applies to: 178-184

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@grunch
grunch merged commit 9a0cc73 into main Aug 25, 2025
1 of 2 checks passed
@grunch
grunch deleted the added/sign-release branch August 25, 2025 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant