# Builds a signed Frosty release APK and publishes a Forgejo release for Obtainium. # Pattern: finamp-fork / jellyfin-android-fork on host runner `desktop-release`. # # Required repository secrets: # RELEASE_KEYSTORE_BASE64, STORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD # CLIENT_ID, SECRET (Twitch Helix app credentials — --dart-define) # # Obtainium: # Source: Forgejo # URL: https://forgejo.fsociety00.cc/api/v1/repos/copilot/frosty/releases # APK filter: frosty-arm64-v8a-v.*\.apk # Trim version: v?([0-9]+\.[0-9]+\.[0-9]+) # Match group: 1 # Private repo → Forgejo API token in Obtainium. name: Release APK on: push: tags: - 'v*' workflow_dispatch: permissions: contents: write concurrency: group: frosty-release cancel-in-progress: true jobs: build-and-release: name: Build & publish release runs-on: desktop-release steps: - name: Clone repository shell: bash env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail rm -rf .git git init . origin_url="${GITHUB_SERVER_URL#https://}" origin_url="${origin_url#http://}" git remote add origin "https://copilot:${FORGEJO_TOKEN}@${origin_url}/${GITHUB_REPOSITORY}.git" git -c http.sslVerify=false fetch --depth=1 origin "${GITHUB_SHA}" git checkout --force FETCH_HEAD test -f pubspec.yaml test -f android/app/build.gradle.kts echo "Checked out ${GITHUB_REPOSITORY}@${GITHUB_SHA}" - name: Set up JDK shell: bash run: | set -euo pipefail pkg_install() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else "$@"; fi } if ! command -v java >/dev/null 2>&1; then if command -v dnf >/dev/null 2>&1; then pkg_install dnf install -y java-21-openjdk-devel elif command -v apt-get >/dev/null 2>&1; then pkg_install apt-get update pkg_install apt-get install -y openjdk-21-jdk elif command -v apk >/dev/null 2>&1; then pkg_install apk add --no-cache openjdk21 fi fi java -version if command -v javac >/dev/null 2>&1; then java_home="$(dirname "$(dirname "$(readlink -f "$(command -v javac)")")")" echo "JAVA_HOME=$java_home" >> "$GITHUB_ENV" fi - name: Set up Android SDK shell: bash run: | set -euo pipefail sdk_candidates=( "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}" "/home/yuna/Android/Sdk" "${HOME}/Android/Sdk" "/opt/android-sdk-linux" "/root/Android/Sdk" ) sdk_dir="" for candidate in "${sdk_candidates[@]}"; do if [ -n "$candidate" ] && [ -d "$candidate/platforms" ] && [ -d "$candidate/build-tools" ]; then sdk_dir="$candidate" break fi done if [ -z "$sdk_dir" ]; then echo "Android SDK not found on desktop-release host." exit 1 fi echo "ANDROID_HOME=$sdk_dir" >> "$GITHUB_ENV" echo "ANDROID_SDK_ROOT=$sdk_dir" >> "$GITHUB_ENV" printf 'sdk.dir=%s\n' "$sdk_dir" > android/local.properties echo "Using Android SDK: $sdk_dir" - name: Set up Flutter shell: bash run: | set -euo pipefail pkg_install() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else "$@"; fi } if ! command -v curl >/dev/null 2>&1; then if command -v apk >/dev/null 2>&1; then pkg_install apk add --no-cache curl bash git unzip xz elif command -v dnf >/dev/null 2>&1; then pkg_install dnf install -y curl git unzip elif command -v apt-get >/dev/null 2>&1; then pkg_install apt-get update pkg_install apt-get install -y curl git unzip fi fi # Prefer pinned SDK already on the desktop-release host. flutter_version="$(awk '/^[[:space:]]*flutter:/{print $2; exit}' pubspec.yaml)" flutter_version="${flutter_version:-3.47.0}" candidates=( "/home/yuna/.local/flutter-${flutter_version}" "${HOME}/.cache/frosty-ci-flutter-${flutter_version}" "/home/yuna/.local/flutter" ) flutter_home="" for c in "${candidates[@]}"; do if [ -x "${c}/bin/flutter" ]; then flutter_home="$c" break fi done if [ -z "$flutter_home" ]; then flutter_home="${HOME}/.cache/frosty-ci-flutter-${flutter_version}" rm -rf "${flutter_home}" git clone --depth 1 --branch "${flutter_version}" https://github.com/flutter/flutter.git "${flutter_home}" fi echo "${flutter_home}/bin" >> "$GITHUB_PATH" echo "PATH=${flutter_home}/bin:${PATH}" >> "$GITHUB_ENV" export PATH="${flutter_home}/bin:${PATH}" flutter --version flutter precache --android - name: Decode keystore shell: bash env: RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }} STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} run: | set -euo pipefail if [ -z "${RELEASE_KEYSTORE_BASE64:-}" ] || [ -z "${STORE_PASSWORD:-}" ] || [ -z "${KEY_ALIAS:-}" ] || [ -z "${KEY_PASSWORD:-}" ]; then echo "Missing signing secrets: RELEASE_KEYSTORE_BASE64, STORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD" exit 1 fi printf '%s' "$RELEASE_KEYSTORE_BASE64" | base64 -d > android/upload-keystore.jks test -s android/upload-keystore.jks { echo "storePassword=${STORE_PASSWORD}" echo "keyPassword=${KEY_PASSWORD}" echo "keyAlias=${KEY_ALIAS}" echo "storeFile=${GITHUB_WORKSPACE}/android/upload-keystore.jks" } > android/key.properties echo "Keystore ready ($(wc -c < android/upload-keystore.jks) bytes)." - name: Resolve version metadata id: version shell: bash run: | set -euo pipefail raw="$(grep -E '^version:' pubspec.yaml | head -1 | awk '{print $2}')" VERSION_NAME="${raw%%+*}" VERSION_CODE="${raw##*+}" [ "$VERSION_CODE" != "$raw" ] || VERSION_CODE=1 SHORT_SHA="${GITHUB_SHA::7}" if [[ "${GITHUB_REF}" == refs/tags/* ]]; then TAG="${GITHUB_REF#refs/tags/}" else TAG="build-${VERSION_NAME}-${VERSION_CODE}-${SHORT_SHA}" fi echo "version_name=${VERSION_NAME}" >> "$GITHUB_OUTPUT" echo "version_code=${VERSION_CODE}" >> "$GITHUB_OUTPUT" echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" echo "tag=${TAG} version=${VERSION_NAME}+${VERSION_CODE}" - name: Build signed release APK id: build shell: bash env: CLIENT_ID: ${{ secrets.CLIENT_ID }} SECRET: ${{ secrets.SECRET }} VERSION_NAME: ${{ steps.version.outputs.version_name }} VERSION_CODE: ${{ steps.version.outputs.version_code }} run: | set -euo pipefail if [ -z "${CLIENT_ID:-}" ] || [ -z "${SECRET:-}" ]; then echo "Missing Twitch secrets CLIENT_ID / SECRET (Helix OAuth app)." exit 1 fi flutter pub get flutter build apk --release \ --build-name="${VERSION_NAME}" \ --build-number="${VERSION_CODE}" \ --dart-define="CLIENT_ID=${CLIENT_ID}" \ --dart-define="SECRET=${SECRET}" mkdir -p dist cp build/app/outputs/flutter-apk/app-release.apk \ "dist/frosty-v${VERSION_NAME}.apk" flutter build apk --release --split-per-abi \ --build-name="${VERSION_NAME}" \ --build-number="${VERSION_CODE}" \ --dart-define="CLIENT_ID=${CLIENT_ID}" \ --dart-define="SECRET=${SECRET}" || true for abi in arm64-v8a armeabi-v7a x86_64; do src="build/app/outputs/flutter-apk/app-${abi}-release.apk" if [ -f "$src" ]; then cp "$src" "dist/frosty-${abi}-v${VERSION_NAME}.apk" fi done ls -lh dist/ - name: Publish Forgejo release shell: bash env: FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAG: ${{ steps.version.outputs.tag }} VERSION_NAME: ${{ steps.version.outputs.version_name }} VERSION_CODE: ${{ steps.version.outputs.version_code }} SHORT_SHA: ${{ steps.version.outputs.short_sha }} run: | set -euo pipefail pkg_install() { if [ "$(id -u)" -eq 0 ]; then "$@" elif command -v sudo >/dev/null 2>&1; then sudo "$@" else return 1; fi } for pkg in curl jq; do command -v "$pkg" >/dev/null 2>&1 && continue if command -v apk >/dev/null 2>&1; then pkg_install apk add --no-cache "$pkg" elif command -v dnf >/dev/null 2>&1; then pkg_install dnf install -y "$pkg" elif command -v apt-get >/dev/null 2>&1; then pkg_install apt-get update pkg_install apt-get install -y "$pkg" fi done API="${GITHUB_API_URL:-${GITHUB_SERVER_URL}/api/v1}/repos/${GITHUB_REPOSITORY}" AUTH="Authorization: token ${FORGEJO_TOKEN}" BODY_FILE="$(mktemp)" cat > "${BODY_FILE}" </dev/null fi test -n "$release_id" && test "$release_id" != "null" for apk in dist/*.apk; do [ -f "$apk" ] || continue name="$(basename "$apk")" echo "Uploading ${name}…" curl -sS -X POST -H "$AUTH" \ -H "Content-Type: application/octet-stream" \ --data-binary @"${apk}" \ "${API}/releases/${release_id}/assets?name=${name}" \ | jq -r '.name // .message' done echo "Published ${TAG} as release ${release_id}" rm -f "$BODY_FILE"