Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/release-body.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ This release is built from the tagged source in this repository. It is intended
- Review [docs/security-hardening.zh-CN.md](./docs/security-hardening.zh-CN.md) before exposing the service beyond localhost or LAN.
- Review [docs/openai-docs-review.zh-CN.md](./docs/openai-docs-review.zh-CN.md) and [docs/app-server-protocol-matrix.zh-CN.md](./docs/app-server-protocol-matrix.zh-CN.md) for Codex App Server / OpenAI API compatibility notes.
- Review [docs/candidate-release-review.zh-CN.md](./docs/candidate-release-review.zh-CN.md) before treating schema drift, MCP/plugin, WebSocket, Realtime, filesystem, terminal, or permission-profile capabilities as stable.
- If Android assets include a debug APK instead of a signed release APK, the repository has not configured Android signing secrets for this run.
- Android assets are published only after the stable official signing certificate passes the release fingerprint gate.

## Assets

- `CX-Codex-<tag>.zip`: source, docs, scripts, and built Web / CLI assets for self-hosted deployment and audit.
- `CX-Codex-<tag>.sha256`: checksum for the release zip.
- `cx-codex-android-<tag>.apk`: signed Android APK, when signing secrets are configured.
- `cx-codex-android-debug-<tag>.apk`: debug APK fallback for self-hosted testing when signing secrets are not configured.
- `cx-codex-android-<tag>.apk`: Android APK signed with the stable official release certificate.
- `*.sha256`: checksum files for each uploaded zip or APK asset.

## Verification
Expand Down Expand Up @@ -47,7 +46,7 @@ Set-ExecutionPolicy Bypass -Scope Process -Force; irm https://raw.githubusercont

On first launch, enter your own CX-Codex service URL. The Android app does not ship with a private server address.

If you previously installed a debug APK and this release provides a signed APK, Android may require uninstalling the debug build before installing the signed build.
Official signed releases can update earlier official signed releases in place. Development builds use a separate application id and do not replace the official app.

Deep-Doze FCM wake is optional and is not enabled by source support alone. The default release workflow does not bundle a project-specific `google-services.json` or server service-account credential. Self-hosters must configure matching Android/server Firebase projects, rebuild the APK, and pass `npm run verify:mobile-push-readiness -- --require-ready` with a real registered device and active task before calling this channel ready. Foreground service, SSE, bounded polling, network recovery, and manual refresh remain available without Firebase.

Expand Down
69 changes: 48 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
ANDROID_DEFAULT_SERVER_URL: ${{ secrets.ANDROID_DEFAULT_SERVER_URL }}
ANDROID_RELEASE_CERT_SHA256: f06cc88920b5f0cdc55c5ddcd51694fcf0131431900dbb24e8bd8820d965d4d4

steps:
- name: Checkout repository
Expand Down Expand Up @@ -68,8 +69,25 @@ jobs:
run: |
sdkmanager --install "platform-tools" "platforms;android-36" "build-tools;36.0.0"

- name: Require Android release signing secrets
shell: pwsh
run: |
$requiredSecrets = [ordered]@{
ANDROID_KEYSTORE_BASE64 = $env:ANDROID_KEYSTORE_BASE64
ANDROID_KEYSTORE_PASSWORD = $env:ANDROID_KEYSTORE_PASSWORD
ANDROID_KEY_ALIAS = $env:ANDROID_KEY_ALIAS
ANDROID_KEY_PASSWORD = $env:ANDROID_KEY_PASSWORD
}
$missingSecrets = @(
$requiredSecrets.GetEnumerator() |
Where-Object { [string]::IsNullOrWhiteSpace([string]$_.Value) } |
ForEach-Object { $_.Key }
)
if ($missingSecrets.Count -gt 0) {
throw "Official Android releases require stable signing secrets. Missing: $($missingSecrets -join ', ')"
}

- name: Prepare Android signing files
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
shell: pwsh
run: |
$keystoreDir = Join-Path $env:RUNNER_TEMP 'android-signing'
Expand All @@ -90,7 +108,6 @@ jobs:
run: npm run mobile:android:sync

- name: Build Android release APK
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
shell: pwsh
env:
APP_VERSION_NAME: ${{ github.ref_name }}
Expand All @@ -99,18 +116,39 @@ jobs:
.\gradlew.bat assembleRelease
Pop-Location

- name: Build Android debug APK fallback
if: ${{ env.ANDROID_KEYSTORE_BASE64 == '' }}
- name: Verify Android release signature
shell: pwsh
env:
APP_VERSION_NAME: ${{ github.ref_name }}
run: |
Push-Location android
.\gradlew.bat assembleDebug
Pop-Location
$apkPath = "android/app/build/outputs/apk/release/app-release.apk"
$sdkRoot = if (-not [string]::IsNullOrWhiteSpace($env:ANDROID_SDK_ROOT)) {
$env:ANDROID_SDK_ROOT
} else {
$env:ANDROID_HOME
}
$apksignerPath = Join-Path $sdkRoot "build-tools/36.0.0/apksigner.bat"
if (-not (Test-Path -LiteralPath $apksignerPath -PathType Leaf)) {
throw "apksigner was not found: $apksignerPath"
}

$certificateOutput = (& $apksignerPath verify --print-certs $apkPath 2>&1 | Out-String)
if ($LASTEXITCODE -ne 0) {
throw "Android release APK signature verification failed."
}
$digestMatch = [regex]::Match(
$certificateOutput,
"Signer #1 certificate SHA-256 digest:\s*(?<digest>[0-9a-fA-F]{64})"
)
if (-not $digestMatch.Success) {
throw "Android release certificate SHA-256 digest was not found."
}
$actualDigest = $digestMatch.Groups["digest"].Value.ToLowerInvariant()
$expectedDigest = $env:ANDROID_RELEASE_CERT_SHA256.ToLowerInvariant()
if ($actualDigest -ne $expectedDigest) {
throw "Android release certificate changed unexpectedly. Expected $expectedDigest but found $actualDigest."
}
Write-Host "Android release signature verified: $actualDigest"

- name: Collect Android release artifacts
if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }}
shell: pwsh
run: |
$outputDir = Join-Path $env:RUNNER_TEMP 'release'
Expand All @@ -120,17 +158,6 @@ jobs:
$hash = (Get-FileHash -Algorithm SHA256 $apkTarget).Hash.ToLowerInvariant()
Set-Content -Path ($apkTarget + '.sha256') -Value "$hash $(Split-Path -Leaf $apkTarget)" -Encoding ASCII

- name: Collect Android debug fallback artifacts
if: ${{ env.ANDROID_KEYSTORE_BASE64 == '' }}
shell: pwsh
run: |
$outputDir = Join-Path $env:RUNNER_TEMP 'release'
$apkSource = "android/app/build/outputs/apk/debug/app-debug.apk"
$apkTarget = Join-Path $outputDir "cx-codex-android-debug-${env:GITHUB_REF_NAME}.apk"
Copy-Item $apkSource $apkTarget -Force
$hash = (Get-FileHash -Algorithm SHA256 $apkTarget).Hash.ToLowerInvariant()
Set-Content -Path ($apkTarget + '.sha256') -Value "$hash $(Split-Path -Leaf $apkTarget)" -Encoding ASCII

- name: Verify release artifact checksums
run: npm run verify:release-artifacts -- -OutputDir "${env:RUNNER_TEMP}\release"

Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Self-hosted OpenAI Codex Web UI and Android client bridge.
## 快速入口

- 最新 Release: [github.com/Qjzn/CX-Codex/releases/latest](https://github.com/Qjzn/CX-Codex/releases/latest)
- 2.5.9 发布说明: [docs/release-notes-2.5.9.zh-CN.md](./docs/release-notes-2.5.9.zh-CN.md)
- 2.5.8 发布说明: [docs/release-notes-2.5.8.zh-CN.md](./docs/release-notes-2.5.8.zh-CN.md)
- 2.5.7 发布说明: [docs/release-notes-2.5.7.zh-CN.md](./docs/release-notes-2.5.7.zh-CN.md)
- 2.5.6 发布说明: [docs/release-notes-2.5.6.zh-CN.md](./docs/release-notes-2.5.6.zh-CN.md)
Expand Down Expand Up @@ -51,7 +52,7 @@ Self-hosted OpenAI Codex Web UI and Android client bridge.
- 原生 Codex 仍在本机运行,`CX-Codex` 只负责把浏览器、手机和远程访问链路做稳。
- 重点解决 Windows 常驻、Android 恢复、长会话状态和远程入口这几类高频真实问题。
- 默认不内置私人服务器地址,不要求上传私有项目、Token 或账号凭据。
- Release 自动产出 Web 包、校验文件,并在没有签名 secret 时提供 Android debug 备用 APK。
- Release 自动产出 Web 包、校验文件和使用固定官方证书签名的 Android APK。

## 核心卖点

Expand Down Expand Up @@ -150,7 +151,9 @@ http://127.0.0.1:7420

默认安装最新正式 Release,并校验 Release 包的 SHA-256;只有参与源码预览时才应显式使用 `-UseBranchArchive`。

安装完成后,电脑本机打开 `http://127.0.0.1:7420/local-setup`,用手机扫描二维码打开临时地址,再输入页面显示的访问密码。二维码只在本机生成且只包含地址,不包含密码;配对页不允许通过公网域名访问。
首次安装通常需要 2–5 分钟。bootstrap 会持续显示安装阶段,并在长步骤中每 15 秒提示仍在运行;不要在构建期间关闭窗口。

安装和公网验证全部完成后,bootstrap 会自动打开电脑本机的 `http://127.0.0.1:7420/local-setup`。用手机扫描二维码打开临时地址,再输入页面显示的访问密码即可;二维码只在本机生成且只包含地址,不包含密码,配对页也不允许通过公网域名访问。无人值守安装可增加 `-SkipOpenPairing`,完成后再手动打开配对页。

`-JsonOutput` 的 stdout 固定为单行 JSON,构建进度和诊断写入 stderr。成功结果包含 `schemaVersion`、`operation`、`version`、`started`、`healthReady`、本机/公网地址和结构化告警;失败结果返回 `BOOTSTRAP_FAILED` 与失败阶段,不输出密码、Cookie 或 Token。bootstrap 还会读取归档内的 `release-capabilities.json`,拒绝把新版参数交给不支持它们的旧 Release。

Expand Down Expand Up @@ -187,9 +190,9 @@ http://127.0.0.1:7420

Release 页面会发布 Android APK:

- 配置签名 secret 时发布正式 `cx-codex-android-<version>.apk`。
- 未配置签名 secret 时发布 `cx-codex-android-debug-<version>.apk`,适合自托管测试和临时安装
- debug 包切换到正式签名包时,Android 可能要求先卸载旧 debug
- 正式 Release 只发布 `cx-codex-android-<version>.apk`,并始终使用同一张官方签名证书
- GitHub 仓库缺少签名配置或证书指纹变化时,发布工作流会直接失败,不会生成可能导致覆盖安装失败的公开 Debug APK
- 本地 Debug 构建使用独立包名 `com.cxcodex.bridge.debug`,可与正式版同时安装

如果你自己构建,请查看:

Expand Down
4 changes: 4 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ android {
}
}
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
}
release {
minifyEnabled false
if (signingPropertiesFile.exists()) {
Expand Down
5 changes: 4 additions & 1 deletion docs/android-shell.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ npm run mobile:android:open

当前工作区已经支持本地签名版 APK:

- 本地 keystore:`%USERPROFILE%\\.cx-codex\\android-signing\\cxcodex-release.jks`
- 本地 keystore:`%USERPROFILE%\\.codexui\\android-signing\\codexui-release.jks`
- 本地签名配置:`android/keystore.properties`

这两个文件只用于当前机器:
Expand Down Expand Up @@ -288,8 +288,11 @@ Android 侧分三段持久收敛:Service 成功启动后才声明事件;权

- 构建 Web Release 包
- 构建签名版 `CX-Codex` Android APK
- 校验 APK 签名证书 SHA-256 与项目固定证书一致
- 将 APK 与 `.sha256` 一并挂到 GitHub Release

正式 Release 不再回退发布 Debug APK。缺少任一签名 secret、签名失败或证书指纹变化都会中止发布。本地 Debug 构建使用 `com.cxcodex.bridge.debug`,不会与正式版 `com.cxcodex.bridge` 发生覆盖安装冲突。

## 已知边界

- 如果没有设置 `CAP_SERVER_URL`,安卓壳仍可生成,首次进入会显示连接地址配置页。
Expand Down
7 changes: 7 additions & 0 deletions docs/changelog.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## 未发布

## 2.5.9 - 2026-07-25

- Android 正式 Release 现在强制使用固定发布证书;缺少签名 secrets、签名失败或证书 SHA-256 与项目固定指纹不一致时,GitHub Release 会直接失败,不再公开发布容易产生覆盖安装冲突的 Debug APK。
- 本地 Debug 构建改用独立包名 `com.cxcodex.bridge.debug`,可以与正式版并存,不再争用正式包名 `com.cxcodex.bridge`。
- Windows 一键安装在子安装器长时间运行时会持续转发诊断,并每 15 秒显示一次无敏感信息的安装心跳;`-JsonOutput` 的 stdout 仍只保留最终单行 JSON。
- `RemoteQuick` 完成本机健康、公网健康、HTTP 鉴权和 WebSocket 鉴权后,会自动打开仅限本机的手机配对页;无人值守环境可用 `-SkipOpenPairing` 禁用自动打开,浏览器启动失败也不会把已经成功的安装误报为失败。

## 2.5.8 - 2026-07-25

- 修复 `RemoteQuick` 已生成临时地址、但公网验证仍处于 `verifying` 时安装器就提前返回 `ok=false` 的竞态;安装结果现在等待运行时进入 `ready` 且健康、HTTP 鉴权、WebSocket 鉴权三项全部通过,短暂验证失败重试期间也不会过早结束。
Expand Down
4 changes: 2 additions & 2 deletions docs/operations-plan.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

1. `npm.cmd run build`
2. Release zip 和 sha256 是否生成
3. Android APK 是否生成;没有签名 secrets 时必须生成 debug 备用 APK
3. Android 正式签名 APK 是否生成,证书 SHA-256 是否与固定发布证书一致
4. README 截图是否脱敏
5. `.github/release-body.md` 是否是当前版本
6. `docs/changelog.zh-CN.md` 是否写明用户可感知变化
Expand Down Expand Up @@ -108,7 +108,7 @@ README、Release 和 GitHub About 要持续覆盖这些关键词:
- README 首屏长期保留 Release、构建、License、Android 和 Windows 入口 badge。
- Issue 模板必须收集版本、平台、访问方式、健康检查和脱敏确认。
- `SUPPORT.md` 作为反馈前置页,减少无效 Issue 和隐私泄露。
- Release workflow 必须保证至少有 Web zip、sha256 Android debug 备用 APK。
- Release workflow 必须保证至少有 Web zip、sha256 和固定证书签名的 Android APK;缺少签名配置时必须停止发布

暂不优先:

Expand Down
43 changes: 43 additions & 0 deletions docs/release-notes-2.5.9.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# CX-Codex 2.5.9:Android 正式签名升级与一键安装反馈优化

2.5.9 修复 Android Release 可能退回发布临时 Debug 签名 APK、导致已有正式版无法覆盖安装的问题。正式 APK 现在只允许使用项目固定证书构建;缺少签名配置或证书指纹变化时,发布会直接失败,不再把问题交给安装用户。

## 本版变化

- GitHub Release 强制校验 Android 正式签名配置与证书 SHA-256,只发布可持续覆盖升级的 `cx-codex-android-v2.5.9.apk`。
- 删除公开 Debug APK 备用发布路径;本地 Debug 构建使用独立包名 `com.cxcodex.bridge.debug`,可与正式版同时安装。
- Windows bootstrap 在耗时步骤中每 15 秒输出一次无敏感信息的安装心跳,避免新人误以为安装卡死。
- `RemoteQuick` 完成本机健康、公网健康、HTTP 鉴权和 WebSocket 鉴权后,自动打开仅限本机访问的手机配对页。
- 无人值守场景可以增加 `-SkipOpenPairing`;浏览器启动失败只返回警告,不会把已经成功的安装误报为失败。

## Android 升级

从此前正式签名版本升级时,直接安装本版 APK 即可保留 App 配置。

如果设备安装的是 v2.5.8 的临时 Debug APK,因为 Android 不允许不同证书覆盖同一包名,需要卸载该 Debug 版本一次,再安装 v2.5.9 正式版。此后正式版本可以持续覆盖升级。

## 一步安装、升级或修复

在普通 Windows PowerShell 中执行:

```powershell
& ([scriptblock]::Create((irm 'https://raw.githubusercontent.com/Qjzn/CX-Codex/main/scripts/bootstrap-windows.ps1'))) -RemoteQuick -JsonOutput
```

`JsonOutput` 模式的 stdout 仍然只输出最终单行 JSON,安装阶段与心跳写入 stderr。完成后会打开本机 `http://127.0.0.1:7420/local-setup`,用手机扫描二维码并输入页面单独显示的访问密码即可。

## 卸载

```powershell
& ([scriptblock]::Create((irm 'https://raw.githubusercontent.com/Qjzn/CX-Codex/main/scripts/uninstall-windows.ps1'))) -JsonOutput
```

`uninstall-windows.ps1` 默认保留 CX-Codex 数据、Codex 登录态、工作区和 Android 签名材料。

## 验证

- Android release APK 通过 APK Signature Scheme 校验,证书 SHA-256 与项目固定发布证书一致。
- Android Debug 与 Release 使用不同 application id。
- Windows 产品化、RemoteQuick、前端/CLI 构建、Release ZIP 与全部 SHA-256 校验通过。

正式标签发布前,本版仍按候选版本审查;只有 GitHub Release 工作流、公开资产和正式签名 APK 全部验证通过后,才视为正式可交付版本。
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cx-codex",
"version": "2.5.8",
"version": "2.5.9",
"description": "CX-Codex Web UI and browser bridge for Windows, Linux, Android, LAN, and remote self-hosted access",
"type": "module",
"license": "MIT",
Expand Down
Loading