Android Release / Build signed release APK (push) Successful in 37m54s
The Android release workflow now creates or updates a Gitea release and attaches the APK and its SHA256 to it. A GITEA_TOKEN with releases: write is required for uploads. The README now documents the Gitea release flow and the required permissions. - Enforces that releases run only on a tag and matches the app version. - Replaces or updates release assets to match tag names. - Requires GITEA_TOKEN with releases: write for uploads.
199 lines
7.4 KiB
YAML
199 lines
7.4 KiB
YAML
name: Android Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build-release-apk:
|
|
name: Build signed release APK
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
permissions:
|
|
code: read
|
|
releases: write
|
|
env:
|
|
ANDROID_COMPILE_SDK: "36"
|
|
ANDROID_BUILD_TOOLS: "36.0.0"
|
|
ANDROID_NDK_VERSION: "30.0.15729638"
|
|
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
|
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
|
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
|
GITEA_RELEASE_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
cache: npm
|
|
|
|
- name: Set up Java
|
|
uses: actions/setup-java@v4
|
|
with:
|
|
distribution: temurin
|
|
java-version: "21"
|
|
cache: gradle
|
|
|
|
- name: Set up Rust
|
|
uses: dtolnay/rust-toolchain@1.95.0
|
|
with:
|
|
targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
|
|
|
|
- name: Cache Rust build
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: src-tauri -> target
|
|
|
|
- name: Set up Android SDK
|
|
uses: android-actions/setup-android@v3
|
|
|
|
- name: Install Android SDK packages
|
|
run: |
|
|
yes | sdkmanager --licenses >/dev/null || true
|
|
sdkmanager \
|
|
"platforms;android-${ANDROID_COMPILE_SDK}" \
|
|
"build-tools;${ANDROID_BUILD_TOOLS}" \
|
|
"ndk;${ANDROID_NDK_VERSION}"
|
|
echo "NDK_HOME=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}" >> "${GITEA_ENV}"
|
|
echo "${ANDROID_HOME}/build-tools/${ANDROID_BUILD_TOOLS}" >> "${GITEA_PATH}"
|
|
|
|
- name: Install JavaScript dependencies
|
|
run: npm ci
|
|
|
|
- name: Check release tag and app version
|
|
run: |
|
|
if [ "${GITHUB_REF_TYPE}" != "tag" ]; then
|
|
echo "Release builds must run on a tag such as v0.1.0." >&2
|
|
exit 1
|
|
fi
|
|
APP_VERSION="$(node -p "require('./src-tauri/tauri.conf.json').version")"
|
|
if [ "${GITHUB_REF_NAME}" != "v${APP_VERSION}" ]; then
|
|
echo "Tag ${GITHUB_REF_NAME} does not match app version v${APP_VERSION}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Validate signing secrets
|
|
run: |
|
|
for SECRET_NAME in \
|
|
ANDROID_KEYSTORE_BASE64 \
|
|
ANDROID_KEYSTORE_PASSWORD \
|
|
ANDROID_KEY_ALIAS \
|
|
ANDROID_KEY_PASSWORD \
|
|
GITEA_RELEASE_TOKEN
|
|
do
|
|
if [ -z "${!SECRET_NAME:-}" ]; then
|
|
echo "Missing Gitea Actions secret: ${SECRET_NAME}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
- name: Restore release keystore
|
|
run: |
|
|
SIGNING_DIRECTORY="${RUNNER_TEMP}/wallpaperflow-signing"
|
|
mkdir -p "${SIGNING_DIRECTORY}"
|
|
printf '%s' "${ANDROID_KEYSTORE_BASE64}" | base64 --decode > "${SIGNING_DIRECTORY}/release.jks"
|
|
chmod 600 "${SIGNING_DIRECTORY}/release.jks"
|
|
echo "ANDROID_SIGNING_KEYSTORE=${SIGNING_DIRECTORY}/release.jks" >> "${GITEA_ENV}"
|
|
|
|
- name: Build unsigned release APK
|
|
run: npm run tauri -- android build --apk --ci
|
|
|
|
- name: Align and sign APK
|
|
run: |
|
|
UNSIGNED_APK="src-tauri/gen/android/app/build/outputs/apk/universal/release/app-universal-release-unsigned.apk"
|
|
RELEASE_DIRECTORY="release"
|
|
ALIGNED_APK="${RELEASE_DIRECTORY}/WallpaperFlow-release-aligned.apk"
|
|
SIGNED_APK="${RELEASE_DIRECTORY}/WallpaperFlow-release.apk"
|
|
|
|
test -f "${UNSIGNED_APK}"
|
|
mkdir -p "${RELEASE_DIRECTORY}"
|
|
zipalign -p -f 4 "${UNSIGNED_APK}" "${ALIGNED_APK}"
|
|
apksigner sign \
|
|
--ks "${ANDROID_SIGNING_KEYSTORE}" \
|
|
--ks-key-alias "${ANDROID_KEY_ALIAS}" \
|
|
--ks-pass env:ANDROID_KEYSTORE_PASSWORD \
|
|
--key-pass env:ANDROID_KEY_PASSWORD \
|
|
--out "${SIGNED_APK}" \
|
|
"${ALIGNED_APK}"
|
|
apksigner verify --verbose --print-certs "${SIGNED_APK}"
|
|
rm "${ALIGNED_APK}"
|
|
(cd "${RELEASE_DIRECTORY}" && sha256sum WallpaperFlow-release.apk > WallpaperFlow-release.apk.sha256)
|
|
|
|
- name: Attach APK to Gitea release
|
|
run: |
|
|
API_ROOT="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}"
|
|
RELEASE_JSON="${RUNNER_TEMP}/wallpaperflow-release.json"
|
|
CREATE_JSON="${RUNNER_TEMP}/wallpaperflow-create-release.json"
|
|
ASSETS_JSON="${RUNNER_TEMP}/wallpaperflow-release-assets.json"
|
|
|
|
STATUS="$(curl --silent --show-error \
|
|
--output "${RELEASE_JSON}" \
|
|
--write-out '%{http_code}' \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
"${API_ROOT}/releases/tags/${GITHUB_REF_NAME}")"
|
|
|
|
if [ "${STATUS}" = "404" ]; then
|
|
node -e '
|
|
process.stdout.write(JSON.stringify({
|
|
tag_name: process.env.GITHUB_REF_NAME,
|
|
name: `WallpaperFlow ${process.env.GITHUB_REF_NAME}`,
|
|
body: "Automatisch erstelltes Android-Release.",
|
|
draft: false,
|
|
prerelease: false
|
|
}))
|
|
' > "${CREATE_JSON}"
|
|
curl --fail --silent --show-error \
|
|
--request POST \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
--header "Content-Type: application/json" \
|
|
--data-binary "@${CREATE_JSON}" \
|
|
--output "${RELEASE_JSON}" \
|
|
"${API_ROOT}/releases"
|
|
elif [ "${STATUS}" != "200" ]; then
|
|
echo "Could not read Gitea release for ${GITHUB_REF_NAME} (HTTP ${STATUS})." >&2
|
|
exit 1
|
|
fi
|
|
|
|
RELEASE_ID="$(node -e '
|
|
const release = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
if (!release.id) process.exit(1);
|
|
process.stdout.write(String(release.id));
|
|
' "${RELEASE_JSON}")"
|
|
|
|
curl --fail --silent --show-error \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
--output "${ASSETS_JSON}" \
|
|
"${API_ROOT}/releases/${RELEASE_ID}/assets"
|
|
|
|
for FILE in \
|
|
release/WallpaperFlow-release.apk \
|
|
release/WallpaperFlow-release.apk.sha256
|
|
do
|
|
ASSET_NAME="$(basename "${FILE}")"
|
|
ASSET_ID="$(ASSET_NAME="${ASSET_NAME}" node -e '
|
|
const assets = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
const asset = assets.find(item => item.name === process.env.ASSET_NAME);
|
|
if (asset) process.stdout.write(String(asset.id));
|
|
' "${ASSETS_JSON}")"
|
|
|
|
if [ -n "${ASSET_ID}" ]; then
|
|
curl --fail --silent --show-error \
|
|
--request DELETE \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
"${API_ROOT}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
|
|
fi
|
|
|
|
curl --fail --silent --show-error \
|
|
--request POST \
|
|
--header "Authorization: token ${GITEA_RELEASE_TOKEN}" \
|
|
--form "attachment=@${FILE}" \
|
|
"${API_ROOT}/releases/${RELEASE_ID}/assets" >/dev/null
|
|
done
|