Files
LockScreenWallpaper/.gitea/workflows/android-release.yml
T
Christoph aca5562f81
Android Release / Build signed release APK (push) Successful in 37m54s
feat(gitea): attach build artifacts to Gitea releases
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.
2026-08-26 00:14:39 +02:00

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