-
Notifications
You must be signed in to change notification settings - Fork 0
Add APK signing configuration to CI/CD workflow #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c1cf1f0
a1bde38
b6e7b14
00c164f
7832336
5933d50
d8c0a03
85df511
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -102,6 +102,59 @@ The app automatically grants WebView permission requests for: | |||||
| - versionCode 必须单调递增 | ||||||
| - 建议 versionCode 使用公式:`主版本*10000 + 次版本*100 + 修订号`(例如 1.0.1 -> 10001) | ||||||
|
|
||||||
| ### Release APK 签名配置 | ||||||
|
|
||||||
| 项目使用固定签名对 Release APK 进行签名。签名配置通过 GitHub Secrets 管理: | ||||||
|
|
||||||
| #### 配置 GitHub Secrets | ||||||
|
|
||||||
| 在仓库的 Settings > Secrets and variables > Actions 中添加以下 secrets: | ||||||
|
|
||||||
| 1. **KEYSTORE_BASE64**: 密钥库文件的 base64 编码 | ||||||
| 2. **KEYSTORE_PASSWORD**: 密钥库密码 | ||||||
| 3. **KEY_ALIAS**: 密钥别名 | ||||||
| 4. **KEY_PASSWORD**: 密钥密码 | ||||||
|
|
||||||
| #### 生成密钥库文件 | ||||||
|
|
||||||
| 如果还没有密钥库文件,可以使用以下命令生成: | ||||||
|
|
||||||
| ```bash | ||||||
| keytool -genkey -v -keystore release.keystore -alias release \ | ||||||
| -keyalg RSA -keysize 2048 -validity 10000 | ||||||
| ``` | ||||||
|
|
||||||
| #### 将密钥库编码为 base64 | ||||||
|
|
||||||
| ```bash | ||||||
| # Linux/macOS | ||||||
| base64 release.keystore | tr -d '\n' > keystore.base64.txt | ||||||
|
|
||||||
| # Windows (PowerShell) | ||||||
| [Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) | Out-File -Encoding ASCII keystore.base64.txt | ||||||
| ``` | ||||||
|
|
||||||
| 然后将 `keystore.base64.txt` 的内容复制到 GitHub Secrets 的 `KEYSTORE_BASE64` 中。 | ||||||
|
|
||||||
| #### 本地构建签名版本 | ||||||
|
|
||||||
| 本地构建需要创建 `keystore.properties` 文件(参考 `keystore.properties.template`): | ||||||
|
|
||||||
| ```properties | ||||||
| storeFile=release.keystore | ||||||
| storePassword=你的密钥库密码 | ||||||
| keyAlias=release | ||||||
| keyPassword=你的密钥密码 | ||||||
| ``` | ||||||
|
|
||||||
| 然后运行: | ||||||
|
|
||||||
| ```bash | ||||||
| ./gradlew assembleRelease | ||||||
| ``` | ||||||
|
|
||||||
| **注意**: `keystore.properties` 和 `*.keystore` 文件已添加到 `.gitignore`,不会被提交到仓库。 | ||||||
|
||||||
| **注意**: `keystore.properties` 和 `*.keystore` 文件已添加到 `.gitignore`,不会被提交到仓库。 | |
| **注意**: `keystore.properties` 和 `*.keystore` 文件已添加到 `.gitignore`,不会被提交到仓库。完整的签名配置说明请参考 [SIGNING_SETUP.md](SIGNING_SETUP.md)。 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,158 @@ | ||||||||||||||||||||||
| # APK 签名配置指南 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 本文档说明如何为项目配置 APK 签名,以便自动构建已签名的 Release APK。 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## 概述 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 项目使用固定的签名配置对 Release APK 进行签名。签名密钥通过 GitHub Secrets 安全管理,不会暴露在代码仓库中。 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## 一、生成签名密钥库 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 1.1 使用 keytool 生成密钥库 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 在命令行中运行以下命令: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| keytool -genkey -v -keystore release.keystore -alias release \ | ||||||||||||||||||||||
| -keyalg RSA -keysize 2048 -validity 10000 | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **参数说明:** | ||||||||||||||||||||||
| - `release.keystore`: 密钥库文件名 | ||||||||||||||||||||||
| - `release`: 密钥别名 | ||||||||||||||||||||||
| - `RSA`: 密钥算法 | ||||||||||||||||||||||
| - `2048`: 密钥长度 | ||||||||||||||||||||||
| - `10000`: 有效期(天数,约27年) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 1.2 填写密钥信息 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 命令会提示你输入以下信息: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. **密钥库密码**(keystore password):请使用强密码 | ||||||||||||||||||||||
| 2. **密钥密码**(key password):可以与密钥库密码相同 | ||||||||||||||||||||||
| 3. **姓名、组织等信息**:根据实际情况填写 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **重要提示:** | ||||||||||||||||||||||
| - 请妥善保管密钥库文件和密码 | ||||||||||||||||||||||
| - 如果丢失密钥库,将无法更新已发布的应用 | ||||||||||||||||||||||
| - 建议将密钥库文件和密码存储在安全的地方(如密码管理器) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## 二、配置 GitHub Secrets | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 2.1 将密钥库编码为 base64 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **Linux/macOS:** | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| base64 release.keystore | tr -d '\n' > keystore.base64.txt | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **Windows PowerShell:** | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```powershell | ||||||||||||||||||||||
| [Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) | Out-File -Encoding ASCII keystore.base64.txt | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 2.2 在 GitHub 中添加 Secrets | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. 打开仓库页面 | ||||||||||||||||||||||
| 2. 点击 **Settings** > **Secrets and variables** > **Actions** | ||||||||||||||||||||||
| 3. 点击 **New repository secret** 添加以下 secrets: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| | Secret 名称 | 值 | 说明 | | ||||||||||||||||||||||
| |------------|---|------| | ||||||||||||||||||||||
| | `KEYSTORE_BASE64` | `keystore.base64.txt` 的内容 | 密钥库文件的 base64 编码 | | ||||||||||||||||||||||
| | `KEYSTORE_PASSWORD` | 你设置的密钥库密码 | 密钥库密码 | | ||||||||||||||||||||||
| | `KEY_ALIAS` | `release` | 密钥别名(与生成时使用的一致) | | ||||||||||||||||||||||
| | `KEY_PASSWORD` | 你设置的密钥密码 | 密钥密码 | | ||||||||||||||||||||||
|
Comment on lines
+64
to
+67
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 2.3 验证配置 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 配置完成后,推送一个新的 tag 来触发自动构建: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| git tag v1.0.1 | ||||||||||||||||||||||
| git push origin v1.0.1 | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 检查 GitHub Actions 工作流是否成功运行,并生成已签名的 Release APK。 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## 三、本地构建已签名版本 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 如果需要在本地构建已签名的 Release APK: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 3.1 创建 keystore.properties 文件 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 在项目根目录创建 `keystore.properties` 文件(参考 `keystore.properties.template`): | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```properties | ||||||||||||||||||||||
| storeFile=release.keystore | ||||||||||||||||||||||
| storePassword=你的密钥库密码 | ||||||||||||||||||||||
| keyAlias=release | ||||||||||||||||||||||
| keyPassword=你的密钥密码 | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 3.2 将密钥库文件放到项目根目录 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 将 `release.keystore` 文件复制到项目根目录。 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 3.3 构建 Release APK | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| ./gradlew assembleRelease | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 构建完成后,已签名的 APK 位于: | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
| app/build/outputs/apk/release/app-release.apk | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| **注意:** `keystore.properties` 和 `*.keystore` 文件已添加到 `.gitignore`,不会被提交到仓库。 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## 四、安全建议 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. **永远不要将密钥库文件或密码提交到代码仓库** | ||||||||||||||||||||||
| 2. **定期备份密钥库文件**(存储在安全的位置) | ||||||||||||||||||||||
| 3. **使用强密码**(至少16位,包含大小写字母、数字和特殊字符) | ||||||||||||||||||||||
| 4. **限制对 GitHub Secrets 的访问权限** | ||||||||||||||||||||||
| 5. **如果密钥库泄露,立即生成新的密钥库并重新发布应用** | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## 五、故障排查 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 5.1 签名失败 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 如果构建时出现签名错误,检查: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 1. GitHub Secrets 是否正确配置 | ||||||||||||||||||||||
| 2. `KEYSTORE_BASE64` 是否包含完整的 base64 编码(没有换行符) | ||||||||||||||||||||||
| 3. 密码和别名是否正确 | ||||||||||||||||||||||
| 4. 密钥库文件是否有效 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 5.2 验证 APK 签名 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| 使用以下命令验证 APK 是否已正确签名: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| # 查看签名信息 | ||||||||||||||||||||||
| keytool -printcert -jarfile app-release.apk | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # 或使用 apksigner | ||||||||||||||||||||||
| apksigner verify --verbose app-release.apk | ||||||||||||||||||||||
|
Comment on lines
+136
to
+140
|
||||||||||||||||||||||
| # 查看签名信息 | |
| keytool -printcert -jarfile app-release.apk | |
| # 或使用 apksigner | |
| apksigner verify --verbose app-release.apk | |
| # 查看签名信息(在项目根目录运行) | |
| keytool -printcert -jarfile app/build/outputs/apk/release/app-release.apk | |
| # 或使用 apksigner(在项目根目录运行) | |
| apksigner verify --verbose app/build/outputs/apk/release/app-release.apk |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,10 +12,50 @@ android { | |
| versionCode 1 | ||
| versionName "1.0.0" | ||
| } | ||
|
|
||
| signingConfigs { | ||
| release { | ||
| // 从环境变量或 keystore.properties 文件读取签名配置 | ||
| def keystorePropertiesFile = rootProject.file("keystore.properties") | ||
| if (keystorePropertiesFile.exists()) { | ||
| def keystoreProperties = new Properties() | ||
| keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) | ||
|
|
||
| storeFile file(keystoreProperties['storeFile']) | ||
| storePassword keystoreProperties['storePassword'] | ||
| keyAlias keystoreProperties['keyAlias'] | ||
| keyPassword keystoreProperties['keyPassword'] | ||
| } else { | ||
| // CI环境使用环境变量(如果未设置,签名配置将为null) | ||
| def keystoreFile = System.getenv("KEYSTORE_FILE") | ||
| def keystorePassword = System.getenv("KEYSTORE_PASSWORD") | ||
| def keyAlias = System.getenv("KEY_ALIAS") | ||
| def keyPassword = System.getenv("KEY_PASSWORD") | ||
|
Comment on lines
+30
to
+33
|
||
|
|
||
| // 检查签名配置是否完整 | ||
| def hasCompleteSigningConfig = [keystoreFile, keystorePassword, keyAlias, keyPassword] | ||
| .every { it?.trim() } | ||
|
|
||
| // 只有当所有环境变量都存在且非空时才配置签名 | ||
| if (hasCompleteSigningConfig) { | ||
| storeFile file(keystoreFile) | ||
| storePassword keystorePassword | ||
| keyAlias keyAlias | ||
| keyPassword keyPassword | ||
| } | ||
| // 如果环境变量未设置,签名配置保持为空(允许构建debug) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| buildTypes { | ||
| release { | ||
| minifyEnabled false | ||
| proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||
| // 只有当签名配置完整时才应用 | ||
| if (signingConfigs.release.storeFile != null) { | ||
| signingConfig signingConfigs.release | ||
| } | ||
| } | ||
| } | ||
| compileOptions { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,14 @@ | ||||||||||||||||||||||||||||||
| # Android 签名配置模板 | ||||||||||||||||||||||||||||||
| # 复制此文件为 keystore.properties 并填写实际值 | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # 密钥库文件路径(相对于项目根目录) | ||||||||||||||||||||||||||||||
| storeFile=release.keystore | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # 密钥库密码 | ||||||||||||||||||||||||||||||
| storePassword=your_keystore_password | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # 密钥别名 | ||||||||||||||||||||||||||||||
| keyAlias=release | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| # 密钥密码 | ||||||||||||||||||||||||||||||
| keyPassword=your_key_password | ||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+14
|
||||||||||||||||||||||||||||||
| storePassword=your_keystore_password | |
| # 密钥别名 | |
| keyAlias=release | |
| # 密钥密码 | |
| keyPassword=your_key_password | |
| storePassword=<your-strong-keystore-password> | |
| # 密钥别名 | |
| keyAlias=release | |
| # 密钥密码 | |
| keyPassword=<your-strong-key-password> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the fallback to handle unsigned APKs is useful, the comment "如果签名失败,仍然复制未签名版本" might be misleading. If signing fails, the build step should fail rather than silently producing an unsigned APK.
Consider either:
This makes it clearer when something unexpected happens.