mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 12:14:00 +00:00
* feat(android): publish signed release APKs * fix(android): preserve fallback corrections * test(android): isolate APK signer fixtures * docs: refresh generated docs map
470 lines
22 KiB
YAML
470 lines
22 KiB
YAML
name: Android Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: Existing stable OpenClaw release tag to receive the signed Android APK
|
|
required: true
|
|
type: string
|
|
release_publish_run_id:
|
|
description: OpenClaw Release Publish run that approved this release
|
|
required: true
|
|
type: string
|
|
release_publish_branch:
|
|
description: Branch used by the approving OpenClaw Release Publish run
|
|
required: true
|
|
type: string
|
|
release_target_sha:
|
|
description: Exact release tag commit resolved by OpenClaw Release Publish
|
|
required: true
|
|
type: string
|
|
direct_release_recovery:
|
|
description: Allow a completed parent run and published release for explicit backfill or recovery
|
|
required: true
|
|
default: false
|
|
type: boolean
|
|
|
|
permissions:
|
|
actions: read
|
|
attestations: write
|
|
contents: write
|
|
id-token: write
|
|
|
|
concurrency:
|
|
group: android-release-${{ inputs.tag }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
NODE_VERSION: "24.15.0"
|
|
|
|
jobs:
|
|
publish_signed_android_apk:
|
|
name: Publish signed Android APK
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
environment: android-release
|
|
steps:
|
|
- name: Checkout release tag
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: refs/tags/${{ inputs.tag }}
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Download parent release approval
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: android-release-approval-${{ inputs.release_publish_run_id }}
|
|
path: ${{ runner.temp }}/android-release-approval
|
|
repository: ${{ github.repository }}
|
|
run-id: ${{ inputs.release_publish_run_id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Validate release approval and target
|
|
env:
|
|
APPROVAL_PATH: ${{ runner.temp }}/android-release-approval/approval.json
|
|
DIRECT_RELEASE_RECOVERY: ${{ inputs.direct_release_recovery && 'true' || 'false' }}
|
|
EXPECTED_WORKFLOW_BRANCH: ${{ inputs.release_publish_branch }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_PUBLISH_RUN_ID: ${{ inputs.release_publish_run_id }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
RELEASE_TARGET_SHA: ${{ inputs.release_target_sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-[1-9][0-9]*)?$ ]]; then
|
|
echo "Android APK publishing requires a final or correction OpenClaw release tag: ${RELEASE_TAG}" >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${EXPECTED_WORKFLOW_BRANCH}" != "main" && ! "${EXPECTED_WORKFLOW_BRANCH}" =~ ^release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
|
echo "release_publish_branch must be main or release/YYYY.M.PATCH." >&2
|
|
exit 1
|
|
fi
|
|
|
|
expected_source_ref="refs/tags/${RELEASE_TAG}"
|
|
if [[ "${GITHUB_REF}" != "${expected_source_ref}" ]]; then
|
|
echo "Android publication must run from ${expected_source_ref}, got ${GITHUB_REF}." >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! "${RELEASE_TARGET_SHA}" =~ ^[a-f0-9]{40}$ ]]; then
|
|
echo "release_target_sha must be a full lowercase commit SHA." >&2
|
|
exit 1
|
|
fi
|
|
|
|
gh attestation verify "${APPROVAL_PATH}" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/openclaw-release-publish.yml" \
|
|
--source-ref "refs/heads/${EXPECTED_WORKFLOW_BRANCH}" \
|
|
--deny-self-hosted-runners
|
|
run_json="$(gh run view "${RELEASE_PUBLISH_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --json workflowName,headBranch,event,status,conclusion,url)"
|
|
printf '%s' "${run_json}" | node scripts/validate-release-publish-approval.mjs
|
|
tag_sha="$(git rev-parse "${RELEASE_TAG}^{commit}")"
|
|
if [[ "${RELEASE_TARGET_SHA}" != "${tag_sha}" ]]; then
|
|
echo "Release target SHA ${RELEASE_TARGET_SHA} does not match ${RELEASE_TAG} (${tag_sha})." >&2
|
|
exit 1
|
|
fi
|
|
|
|
release_json="$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json tagName,isDraft,isPrerelease,assets,url)"
|
|
if [[ "$(printf '%s' "${release_json}" | jq -r '.tagName')" != "${RELEASE_TAG}" ]]; then
|
|
echo "GitHub release tag does not match ${RELEASE_TAG}." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$(printf '%s' "${release_json}" | jq -r '.isPrerelease')" == "true" ]]; then
|
|
echo "Android APK publishing requires a stable GitHub release." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${DIRECT_RELEASE_RECOVERY}" != "true" && "$(printf '%s' "${release_json}" | jq -r '.isDraft')" != "true" ]]; then
|
|
echo "Normal Android promotion requires the target GitHub release to remain a draft." >&2
|
|
exit 1
|
|
fi
|
|
unexpected_assets="$(printf '%s' "${release_json}" | jq -r '[.assets[]? | select(.name | startswith("OpenClaw-Android")) | .name] | join(", ")')"
|
|
if [[ -n "${unexpected_assets}" ]]; then
|
|
echo "Target release already contains Android assets: ${unexpected_assets}. Immutable recovery accepts them only after rebuilding identical bytes." >&2
|
|
fi
|
|
|
|
- name: Verify release source and Android version
|
|
id: release_source
|
|
env:
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
release_version="${RELEASE_TAG#v}"
|
|
android_release_version="${release_version%%-*}"
|
|
package_version="$(node -p 'require("./package.json").version')"
|
|
android_version="$(node -p 'require("./apps/android/version.json").version')"
|
|
fallback_base_tag=""
|
|
fallback_base_sha=""
|
|
if [[ "${package_version}" == "${release_version}" ]]; then
|
|
:
|
|
elif [[ "${RELEASE_TAG}" =~ -[1-9][0-9]*$ && "${package_version}" == "${android_release_version}" ]]; then
|
|
fallback_base_tag="v${package_version}"
|
|
fallback_base_sha="$(git rev-parse "${fallback_base_tag}^{commit}")"
|
|
release_sha="$(git rev-parse HEAD)"
|
|
if [[ "${fallback_base_sha}" != "${release_sha}" ]]; then
|
|
echo "Fallback correction ${RELEASE_TAG} must resolve to the same source commit as ${fallback_base_tag}." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "Release tag version ${release_version} does not match package.json ${package_version} or a same-commit fallback correction." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${android_version}" != "${android_release_version}" ]]; then
|
|
echo "Android version ${android_version} does not match release train ${android_release_version}." >&2
|
|
exit 1
|
|
fi
|
|
echo "fallback_base_tag=${fallback_base_tag}" >> "${GITHUB_OUTPUT}"
|
|
echo "FALLBACK_ANDROID_BASE_TAG=${fallback_base_tag}" >> "${GITHUB_ENV}"
|
|
echo "FALLBACK_ANDROID_BASE_SHA=${fallback_base_sha}" >> "${GITHUB_ENV}"
|
|
git diff --exit-code
|
|
|
|
- name: Setup Node environment
|
|
uses: ./.github/actions/setup-node-env
|
|
with:
|
|
install-bun: "true"
|
|
install-deps: "false"
|
|
use-actions-cache: "false"
|
|
|
|
- name: Setup Java
|
|
uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5
|
|
with:
|
|
distribution: temurin
|
|
java-version: 17
|
|
cache: gradle
|
|
cache-dependency-path: |
|
|
apps/android/**/*.gradle*
|
|
apps/android/**/gradle-wrapper.properties
|
|
apps/android/gradle/libs.versions.toml
|
|
|
|
- name: Cache Android SDK
|
|
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
|
with:
|
|
path: ~/.android-sdk
|
|
key: ${{ runner.os }}-android-sdk-v1-cmdline-14742923-platform-36-build-tools-36.0.0
|
|
restore-keys: |
|
|
${{ runner.os }}-android-sdk-v1-
|
|
|
|
- name: Setup Android SDK
|
|
run: |
|
|
set -euo pipefail
|
|
ANDROID_SDK_ROOT="$HOME/.android-sdk"
|
|
CMDLINE_TOOLS_VERSION="14742923"
|
|
ARCHIVE="commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip"
|
|
URL="https://dl.google.com/android/repository/${ARCHIVE}"
|
|
if [[ ! -x "${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" ]]; then
|
|
mkdir -p "${ANDROID_SDK_ROOT}/cmdline-tools"
|
|
curl -fsSL "${URL}" -o "${RUNNER_TEMP}/${ARCHIVE}"
|
|
rm -rf "${ANDROID_SDK_ROOT}/cmdline-tools/latest"
|
|
unzip -q "${RUNNER_TEMP}/${ARCHIVE}" -d "${ANDROID_SDK_ROOT}/cmdline-tools"
|
|
mv "${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools" "${ANDROID_SDK_ROOT}/cmdline-tools/latest"
|
|
fi
|
|
echo "ANDROID_SDK_ROOT=${ANDROID_SDK_ROOT}" >> "${GITHUB_ENV}"
|
|
echo "ANDROID_HOME=${ANDROID_SDK_ROOT}" >> "${GITHUB_ENV}"
|
|
echo "${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin" >> "${GITHUB_PATH}"
|
|
echo "${ANDROID_SDK_ROOT}/platform-tools" >> "${GITHUB_PATH}"
|
|
|
|
- name: Install Android SDK packages
|
|
run: |
|
|
set -eu
|
|
yes | sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --licenses >/dev/null
|
|
sdkmanager --sdk_root="${ANDROID_SDK_ROOT}" --install \
|
|
"platform-tools" \
|
|
"platforms;android-36" \
|
|
"build-tools;36.0.0"
|
|
|
|
- name: Create apps-signing read token
|
|
if: ${{ steps.release_source.outputs.fallback_base_tag == '' }}
|
|
id: apps-signing-token
|
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
|
|
with:
|
|
app-id: "2729701"
|
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
|
owner: openclaw
|
|
repositories: apps-signing
|
|
permission-contents: read
|
|
|
|
- name: Checkout encrypted Android signing assets
|
|
if: ${{ steps.release_source.outputs.fallback_base_tag == '' }}
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
repository: openclaw/apps-signing
|
|
ref: main
|
|
path: apps/android/build/release-signing/apps-signing
|
|
token: ${{ steps.apps-signing-token.outputs.token }}
|
|
persist-credentials: false
|
|
|
|
- name: Materialize Android release signing
|
|
if: ${{ steps.release_source.outputs.fallback_base_tag == '' }}
|
|
env:
|
|
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ -z "${MATCH_PASSWORD}" ]]; then
|
|
echo "Android release signing secrets are unavailable." >&2
|
|
exit 1
|
|
fi
|
|
node scripts/android-release-signing.mjs --mode materialize
|
|
|
|
- name: Prepare and verify signed Android APK
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
if [[ -n "${FALLBACK_ANDROID_BASE_TAG}" ]]; then
|
|
base_dir="${RUNNER_TEMP}/fallback-android-release"
|
|
mkdir -p "${base_dir}"
|
|
gh release download "${FALLBACK_ANDROID_BASE_TAG}" --repo "${GITHUB_REPOSITORY}" \
|
|
--pattern OpenClaw-Android.apk \
|
|
--pattern OpenClaw-Android-SHA256SUMS.txt \
|
|
--dir "${base_dir}"
|
|
(
|
|
cd "${base_dir}"
|
|
sha256sum --strict --check OpenClaw-Android-SHA256SUMS.txt
|
|
)
|
|
gh attestation verify "${base_dir}/OpenClaw-Android.apk" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/android-release.yml" \
|
|
--source-ref "refs/tags/${FALLBACK_ANDROID_BASE_TAG}" \
|
|
--source-digest "${FALLBACK_ANDROID_BASE_SHA}" \
|
|
--deny-self-hosted-runners
|
|
bun apps/android/scripts/build-release-artifacts.ts \
|
|
--verify-apk "${base_dir}/OpenClaw-Android.apk"
|
|
cp "${base_dir}/OpenClaw-Android.apk" dist/OpenClaw-Android.apk
|
|
cp "${base_dir}/OpenClaw-Android-SHA256SUMS.txt" dist/OpenClaw-Android-SHA256SUMS.txt
|
|
else
|
|
node --input-type=module <<'NODE'
|
|
import { readFileSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
const path = "apps/android/build/release-signing/gradle.properties";
|
|
const expected = new Set([
|
|
"OPENCLAW_ANDROID_STORE_FILE",
|
|
"OPENCLAW_ANDROID_STORE_PASSWORD",
|
|
"OPENCLAW_ANDROID_KEY_ALIAS",
|
|
"OPENCLAW_ANDROID_KEY_PASSWORD",
|
|
]);
|
|
const properties = new Map();
|
|
for (const rawLine of readFileSync(path, "utf8").split(/\r?\n/u)) {
|
|
const line = rawLine.trim();
|
|
if (!line || line.startsWith("#")) continue;
|
|
const separator = line.indexOf("=");
|
|
if (separator <= 0) throw new Error("Malformed Android signing property.");
|
|
properties.set(line.slice(0, separator).trim(), line.slice(separator + 1).trim());
|
|
}
|
|
for (const name of expected) {
|
|
const value = properties.get(name);
|
|
if (!value) throw new Error(`Missing Android signing property: ${name}`);
|
|
process.env[`ORG_GRADLE_PROJECT_${name}`] = value;
|
|
}
|
|
const result = spawnSync(
|
|
"bun",
|
|
["apps/android/scripts/build-release-artifacts.ts", "--artifact", "third-party"],
|
|
{ env: process.env, stdio: "inherit" },
|
|
);
|
|
process.exit(result.status ?? 1);
|
|
NODE
|
|
|
|
version="$(node -p 'require("./apps/android/version.json").version')"
|
|
source_apk="apps/android/build/release-artifacts/openclaw-${version}-third-party-release.apk"
|
|
source_checksum="${source_apk}.sha256"
|
|
test -s "${source_apk}"
|
|
test -s "${source_checksum}"
|
|
cp "${source_apk}" dist/OpenClaw-Android.apk
|
|
(
|
|
cd dist
|
|
sha256sum OpenClaw-Android.apk > OpenClaw-Android-SHA256SUMS.txt
|
|
)
|
|
fi
|
|
|
|
expected_version_name="${RELEASE_TAG#v}"
|
|
expected_version_name="${expected_version_name%%-*}"
|
|
expected_version_code="$(node -p 'require("./apps/android/version.json").versionCode')"
|
|
actual_app_id="$(apkanalyzer manifest application-id dist/OpenClaw-Android.apk)"
|
|
actual_version_name="$(apkanalyzer manifest version-name dist/OpenClaw-Android.apk)"
|
|
actual_version_code="$(apkanalyzer manifest version-code dist/OpenClaw-Android.apk)"
|
|
if [[ "${actual_app_id}" != "ai.openclaw.app" ]]; then
|
|
echo "Standalone APK has unexpected application ID ${actual_app_id}." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "${actual_version_name}" != "${expected_version_name}" || "${actual_version_code}" != "${expected_version_code}" ]]; then
|
|
echo "Standalone APK version metadata does not match apps/android/version.json." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${FALLBACK_ANDROID_BASE_TAG}" ]]; then
|
|
echo "Reusing verified Android APK from ${FALLBACK_ANDROID_BASE_TAG} for same-commit fallback correction ${RELEASE_TAG}."
|
|
elif [[ "${RELEASE_TAG}" =~ -([1-9][0-9]*)$ ]]; then
|
|
correction_number="${BASH_REMATCH[1]}"
|
|
releases_json="$(gh api --paginate --slurp "repos/${GITHUB_REPOSITORY}/releases?per_page=100")"
|
|
previous_tag="$(printf '%s' "${releases_json}" | jq -r \
|
|
--arg base_tag "v${expected_version_name}" \
|
|
--argjson correction_number "${correction_number}" '
|
|
[
|
|
.[][]
|
|
| select(.draft == false and .prerelease == false)
|
|
| select(any(.assets[]?; .name == "OpenClaw-Android.apk"))
|
|
| select(any(.assets[]?; .name == "OpenClaw-Android-SHA256SUMS.txt"))
|
|
| if .tag_name == $base_tag then
|
|
{ tag: .tag_name, number: 0 }
|
|
elif (.tag_name | startswith($base_tag + "-")) then
|
|
(.tag_name | ltrimstr($base_tag + "-")) as $suffix
|
|
| select($suffix | test("^[1-9][0-9]*$"))
|
|
| { tag: .tag_name, number: ($suffix | tonumber) }
|
|
else empty end
|
|
| select(.number < $correction_number)
|
|
]
|
|
| sort_by(.number)
|
|
| last
|
|
| .tag // empty
|
|
')"
|
|
if [[ -n "${previous_tag}" ]]; then
|
|
previous_dir="${RUNNER_TEMP}/previous-android-release"
|
|
mkdir -p "${previous_dir}"
|
|
gh release download "${previous_tag}" --repo "${GITHUB_REPOSITORY}" \
|
|
--pattern OpenClaw-Android.apk \
|
|
--pattern OpenClaw-Android-SHA256SUMS.txt \
|
|
--dir "${previous_dir}"
|
|
(
|
|
cd "${previous_dir}"
|
|
sha256sum --strict --check OpenClaw-Android-SHA256SUMS.txt
|
|
)
|
|
gh attestation verify "${previous_dir}/OpenClaw-Android.apk" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/android-release.yml" \
|
|
--source-ref "refs/tags/${previous_tag}" \
|
|
--deny-self-hosted-runners
|
|
bun apps/android/scripts/build-release-artifacts.ts \
|
|
--verify-apk "${previous_dir}/OpenClaw-Android.apk"
|
|
previous_version_code="$(apkanalyzer manifest version-code "${previous_dir}/OpenClaw-Android.apk")"
|
|
if (( actual_version_code <= previous_version_code )); then
|
|
echo "Android correction versionCode ${actual_version_code} must exceed ${previous_tag} versionCode ${previous_version_code}." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "No earlier verified standalone APK exists for ${expected_version_name}; accepting this correction as the standalone channel bootstrap."
|
|
fi
|
|
fi
|
|
|
|
- name: Attest Android APK provenance
|
|
uses: actions/attest@a1948c3f048ba23858d222213b7c278aabede763 # v4.1.1
|
|
with:
|
|
subject-path: dist/OpenClaw-Android.apk
|
|
|
|
- name: Upload immutable Android release assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
attach_or_verify_asset() {
|
|
local source_path="$1"
|
|
local asset_name="$2"
|
|
local existing_dir="${RUNNER_TEMP}/existing-${asset_name}"
|
|
if gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json assets |
|
|
jq -e --arg name "${asset_name}" 'any(.assets[]?; .name == $name)' >/dev/null; then
|
|
rm -rf "${existing_dir}"
|
|
mkdir -p "${existing_dir}"
|
|
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" \
|
|
--pattern "${asset_name}" --dir "${existing_dir}"
|
|
cmp --silent "${source_path}" "${existing_dir}/${asset_name}" || {
|
|
echo "Existing Android release asset ${asset_name} differs from this exact-tag build." >&2
|
|
exit 1
|
|
}
|
|
return
|
|
fi
|
|
gh release upload "${RELEASE_TAG}" "${source_path}#${asset_name}" --repo "${GITHUB_REPOSITORY}"
|
|
}
|
|
|
|
attach_or_verify_asset dist/OpenClaw-Android.apk OpenClaw-Android.apk
|
|
attach_or_verify_asset dist/OpenClaw-Android-SHA256SUMS.txt OpenClaw-Android-SHA256SUMS.txt
|
|
|
|
- name: Verify published Android release assets
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
verify_dir="${RUNNER_TEMP}/verify-android-release"
|
|
mkdir -p "${verify_dir}"
|
|
gh release download "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" \
|
|
--pattern OpenClaw-Android.apk \
|
|
--pattern OpenClaw-Android-SHA256SUMS.txt \
|
|
--dir "${verify_dir}"
|
|
(
|
|
cd "${verify_dir}"
|
|
sha256sum --strict --check OpenClaw-Android-SHA256SUMS.txt
|
|
)
|
|
gh attestation verify "${verify_dir}/OpenClaw-Android.apk" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/android-release.yml" \
|
|
--source-ref "refs/tags/${RELEASE_TAG}" \
|
|
--deny-self-hosted-runners
|
|
bun apps/android/scripts/build-release-artifacts.ts \
|
|
--verify-apk "${verify_dir}/OpenClaw-Android.apk"
|
|
|
|
release_json="$(gh release view "${RELEASE_TAG}" --repo "${GITHUB_REPOSITORY}" --json assets)"
|
|
expected_names='["OpenClaw-Android-SHA256SUMS.txt","OpenClaw-Android.apk"]'
|
|
actual_names="$(printf '%s' "${release_json}" | jq -c '[.assets[]? | select(.name | startswith("OpenClaw-Android")) | .name] | sort')"
|
|
if [[ "${actual_names}" != "${expected_names}" ]]; then
|
|
echo "Android release asset names do not match the canonical contract." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Remove materialized signing files
|
|
if: ${{ always() }}
|
|
run: rm -rf apps/android/build/release-signing
|
|
|
|
- name: Summary
|
|
env:
|
|
RELEASE_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
{
|
|
echo "## Signed Android APK published"
|
|
echo
|
|
echo "- APK: https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/OpenClaw-Android.apk"
|
|
echo "- Checksums: https://github.com/${GITHUB_REPOSITORY}/releases/download/${RELEASE_TAG}/OpenClaw-Android-SHA256SUMS.txt"
|
|
echo "- Provenance: \`gh attestation verify OpenClaw-Android.apk --repo ${GITHUB_REPOSITORY} --signer-workflow ${GITHUB_REPOSITORY}/.github/workflows/android-release.yml\`"
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|