Skip to content

Commit 6d8b4a1

Browse files
Add Android release workflow
1 parent b948418 commit 6d8b4a1

6 files changed

Lines changed: 185 additions & 1 deletion

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
name: Android Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "android-v*"
7+
8+
permissions:
9+
contents: write
10+
11+
concurrency:
12+
group: android-release-${{ github.ref }}
13+
cancel-in-progress: false
14+
15+
jobs:
16+
android-apk:
17+
name: Build and publish signed APK
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- uses: actions/setup-java@v4
23+
with:
24+
distribution: temurin
25+
java-version: 17
26+
27+
- uses: android-actions/setup-android@v4
28+
with:
29+
accept-android-sdk-licenses: true
30+
packages: ""
31+
32+
- name: Install Android SDK packages
33+
shell: bash
34+
run: |
35+
set -euo pipefail
36+
sdkmanager "platforms;android-35" "build-tools;35.0.0" "platform-tools"
37+
38+
- name: Read Android version
39+
id: version
40+
shell: bash
41+
run: |
42+
set -euo pipefail
43+
version_name="$(grep -E 'versionName[[:space:]]*=' android/app/build.gradle.kts | sed -E 's/.*"([^"]+)".*/\1/' | head -n 1)"
44+
version_code="$(grep -E 'versionCode[[:space:]]*=' android/app/build.gradle.kts | sed -E 's/.*=[[:space:]]*([0-9]+).*/\1/' | head -n 1)"
45+
test -n "$version_name"
46+
test -n "$version_code"
47+
expected_tag="android-v$version_name"
48+
if [ "$GITHUB_REF_NAME" != "$expected_tag" ]; then
49+
echo "::error::Tag $GITHUB_REF_NAME does not match Android versionName $version_name. Expected $expected_tag."
50+
exit 1
51+
fi
52+
echo "version_name=$version_name" >> "$GITHUB_OUTPUT"
53+
echo "version_code=$version_code" >> "$GITHUB_OUTPUT"
54+
55+
- name: Decode release keystore
56+
shell: bash
57+
env:
58+
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
59+
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
60+
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
61+
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
62+
run: |
63+
set -euo pipefail
64+
for name in ANDROID_KEYSTORE_BASE64 ANDROID_KEYSTORE_PASSWORD ANDROID_KEY_ALIAS ANDROID_KEY_PASSWORD; do
65+
if [ -z "${!name:-}" ]; then
66+
echo "::error::$name secret is required for Android release signing."
67+
exit 1
68+
fi
69+
done
70+
keystore_file="$RUNNER_TEMP/mebius-code-android-release.jks"
71+
printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode > "$keystore_file"
72+
echo "ANDROID_KEYSTORE_FILE=$keystore_file" >> "$GITHUB_ENV"
73+
74+
- name: Build signed release APK
75+
shell: bash
76+
env:
77+
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
78+
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
79+
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
80+
run: |
81+
set -euo pipefail
82+
chmod +x android/gradlew
83+
android/gradlew -p android :app:testDebugUnitTest :app:assembleRelease
84+
85+
- name: Stage release assets
86+
id: stage
87+
shell: bash
88+
run: |
89+
set -euo pipefail
90+
mkdir -p release
91+
apk="$(find android/app/build/outputs/apk/release -maxdepth 1 -name '*.apk' ! -name '*unsigned*' | head -n 1)"
92+
if [ -z "$apk" ]; then
93+
echo "::error::Signed release APK was not found."
94+
find android/app/build/outputs/apk/release -maxdepth 1 -type f -print || true
95+
exit 1
96+
fi
97+
apk_name="Mebius-Code-android-${{ steps.version.outputs.version_name }}.apk"
98+
cp "$apk" "release/$apk_name"
99+
100+
sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
101+
test -n "$sdk_root"
102+
apksigner="$(find "$sdk_root/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
103+
test -n "$apksigner"
104+
"$apksigner" verify --verbose "release/$apk_name"
105+
106+
(cd release && sha256sum "$apk_name" > SHA256SUMS)
107+
cat > "$RUNNER_TEMP/android-release-notes.md" <<EOF
108+
Signed Android APK for Mebius Code.
109+
110+
- Version: ${{ steps.version.outputs.version_name }} (${{ steps.version.outputs.version_code }})
111+
- Default API: http://182.92.150.169/api
112+
- The API address can be changed from the login and settings screens.
113+
- This is a GitHub Release sideload APK; app store submission is not part of this release path.
114+
115+
Download the APK asset and use SHA256SUMS to verify it before installing when possible.
116+
EOF
117+
118+
echo "apk_name=$apk_name" >> "$GITHUB_OUTPUT"
119+
echo "release_name=Mebius Code Android ${{ steps.version.outputs.version_name }}" >> "$GITHUB_OUTPUT"
120+
121+
- name: Publish GitHub Release
122+
uses: softprops/action-gh-release@v2
123+
with:
124+
name: ${{ steps.stage.outputs.release_name }}
125+
body_path: ${{ runner.temp }}/android-release-notes.md
126+
files: |
127+
release/${{ steps.stage.outputs.apk_name }}
128+
release/SHA256SUMS
129+
fail_on_unmatched_files: true

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ coverage/
1414
.env.*
1515
!.env.example
1616

17+
# Android signing keys
18+
*.jks
19+
*.keystore
20+
*.p12
21+
1722
# local runtime data
1823
backend/workspaces/
1924
backend/uploads/

‎README.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,28 @@ emulator development, manually change the login screen API URL to
130130
`http://10.0.2.2:3000/api`. The API address can still be changed from the login
131131
and settings screens. APKs are published from GitHub Releases and can be
132132
installed directly without app store submission.
133+
134+
Download signed Android APKs from the repository
135+
[Releases](https://github.com/AlbertTeslaWizard/Mebius-Code/releases). Android
136+
releases use independent tags such as `android-v0.1.0` and publish an asset
137+
named `Mebius-Code-android-0.1.0.apk` with `SHA256SUMS`.
138+
139+
Maintainers publish a new Android APK by configuring these GitHub Secrets and
140+
pushing an `android-v*` tag:
141+
142+
```text
143+
ANDROID_KEYSTORE_BASE64
144+
ANDROID_KEYSTORE_PASSWORD
145+
ANDROID_KEY_ALIAS
146+
ANDROID_KEY_PASSWORD
147+
```
148+
149+
The release keystore must be long-lived. Replacing it later can prevent users
150+
from installing updates over an existing APK.
151+
152+
For the first Android release:
153+
154+
```bash
155+
git tag android-v0.1.0
156+
git push origin android-v0.1.0
157+
```

‎android/README.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,27 @@ For local emulator development, manually change the login screen API URL to
3434
Users can still change the API address before signing in or later from Settings.
3535
Course demo releases are distributed as signed APKs through GitHub Releases; app
3636
store submission is not required for the course demo path.
37+
38+
## Release
39+
40+
Android releases are published independently from the TUI/npm release line. A
41+
tag such as `android-v0.1.0` triggers `.github/workflows/android-release.yml`,
42+
which builds a signed release APK and uploads it to GitHub Releases as:
43+
44+
```text
45+
Mebius-Code-android-0.1.0.apk
46+
```
47+
48+
The workflow requires these repository secrets:
49+
50+
```text
51+
ANDROID_KEYSTORE_BASE64
52+
ANDROID_KEYSTORE_PASSWORD
53+
ANDROID_KEY_ALIAS
54+
ANDROID_KEY_PASSWORD
55+
```
56+
57+
Generate and keep one long-lived release keystore. Do not commit the keystore to
58+
the repository; replacing it later can prevent installed APKs from upgrading.
59+
The tag must match `versionName`; for example `android-v0.1.0` requires
60+
`versionName = "0.1.0"`.

‎docs/design.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ local 项目的唯一性以 ownerId + 标准化 realpath 为准。Windows 下需
442442

443443
上线验收包括:公网首页返回 `200 OK`,`GET http://182.92.150.169/api/health` 返回健康状态,管理员账号可通过 `POST /api/auth/login` 获取 JWT,并可继续访问 `GET /api/auth/me` 确认为 `admin` 角色。由于演示环境未配置 SMTP,初始管理员账号通过一次性数据库写入创建,凭据只保存在服务器 root 用户私有文件中,不写入公开文档或源码。
444444

445-
多端发布通过 `.github/workflows/release.yml` 统一编排。推送 `v*` tag 后,TUI job 在 Ubuntu、macOS Intel、macOS arm64 和 Windows runner 上运行 typecheck、单元测试和 OpenTUI 原生编译,产出 `mebius-linux-x64.tar.gz`、`mebius-darwin-x64.tar.gz`、`mebius-darwin-arm64.tar.gz` 和 `mebius-windows-x64.zip`;Android job 解码签名 keystore secret 并构建 `Mebius-Code-android-<tag>.apk`;发布 job 汇总二进制、APK、`install.sh`、`install.ps1` 和 `SHA256SUMS` 到 GitHub Release;最后 npm job 发布 `mebius-code` 包。npm 安装、curl 安装和 PowerShell 安装都复用同一组 Release 资产,减少平台差异。
445+
多端发布拆分为两个 GitHub Actions workflow。`.github/workflows/release.yml` 继续处理 `v*` tag 的 TUI native 二进制和 npm 包发布;`.github/workflows/android-release.yml` 处理 `android-v*` tag 的 Android APK 发布。Android workflow 在 Ubuntu runner 上安装 JDK 17 和 Android SDK,读取 `versionName` 校验 tag,一次性解码签名 keystore secret,构建并校验 `Mebius-Code-android-<version>.apk`,再把 APK 和 `SHA256SUMS` 上传到 GitHub Releases。这样 TUI/npm 与 Android 的版本线互不干扰,但仍共享同一个公开仓库和 Release 入口。
446446

447447
TUI 本机测试时,MVP 要求手动启动 PostgreSQL 和 NestJS API,不由 `mebius` 自动拉起后端。local workspace 需要设置 `MEBIUS_CODE_SERVER_MODE=local_runtime` 和 `MEBIUS_CODE_LOCAL_WORKSPACES_ENABLED=true`,且不能使用 production API 容器绑定 Windows 或用户本机路径。远程 API 模式下,TUI 只操作远程后端已有 workspace。公网发布版默认进入 remote API mode,连接 `http://182.92.150.169/api`,因此不会把用户本机路径注册到云端后端。
448448

‎docs/project-management.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ AI 没有直接决定项目总结内容。项目总结部分按课程要求保
9797
| 2026-06-16 | 完成公网部署与验收。 | 将 Mebius Code 部署到云服务器 `182.92.150.169`,配置 Nginx 静态前端和 `/api` 反向代理、PostgreSQL Docker 容器、systemd 后端服务、swap、UFW 和安全组放行,并完成公网首页、健康检查和管理员登录验证。 |
9898
| 2026-06-16 | 完善 TUI 与 Android 分发方案。 | TUI npm 包名确定为 `mebius-code`,支持 `npm install -g mebius-code`、Unix `curl | bash` 和 Windows PowerShell 安装;Android release 默认连接公网 API,并通过 GitHub Releases 发布签名 APK。 |
9999
| 2026-06-16 | 增加自动发布流水线。 | 新增 tag 触发的 GitHub Actions release workflow,自动构建 TUI 多平台原生二进制、Android release APK、安装脚本、`SHA256SUMS`、GitHub Release 和 npm 包。 |
100+
| 2026-06-18 | 拆分 Android 独立发布流水线。 | Android APK 改为由 `android-v*` tag 触发的独立 workflow 构建、签名和发布,产物命名为 `Mebius-Code-android-<version>.apk`,并附带 `SHA256SUMS`。 |
100101
| 2026-06-16 | 统一用户昵称字段并新增 Web 修改密码功能。 | 注册资料从“姓名”调整为“昵称”,后端增加 `name` 到 `nickname` 的数据库迁移;Web 新增账户安全页,支持已登录用户提交当前密码和新密码修改登录密码。 |
101102
| 2026-06-16 | 验证并部署账户安全更新。 | 后端单元测试覆盖当前密码错误、新密码与旧密码相同和成功修改密码哈希;Web/TUI/Android 类型检查、测试或构建通过,并将新前后端产物部署到公网演示服务器。 |
102103

0 commit comments

Comments
 (0)