name: Build Android Release on: push: branches: - main - master tags: - "*" workflow_dispatch: permissions: actions: read contents: read releases: write jobs: android: name: Build Android runs-on: ubuntu-latest steps: - name: Preflight disk cleanup shell: bash run: | set -euo pipefail run_sudo() { if command -v sudo >/dev/null 2>&1; then sudo "$@" else "$@" fi } free_mb() { df -Pm "${1:-/}" | awk 'NR==2 {print $4}' } echo "Disk usage before cleanup:" df -h if command -v docker >/dev/null 2>&1; then docker system prune -af --volumes || true docker builder prune -af || true fi run_sudo rm -rf /tmp/* /var/tmp/* || true if [ "$(free_mb /)" -lt 8192 ]; then echo "Free disk space is below 8GB. Cleaning user build caches..." rm -rf "$HOME/.gradle/caches" "$HOME/.gradle/wrapper/dists" "$HOME/.pub-cache/_temp" || true fi echo "Disk usage after cleanup:" df -h if [ "$(free_mb /)" -lt 4096 ]; then echo "Free disk space is still below 4GB after cleanup. Runner disk is effectively full." exit 1 fi - name: Checkout uses: actions/checkout@v4 with: repository: gongyun/app ref: ${{ github.ref }} token: ${{ secrets.GITEA_TOKEN }} github-server-url: https://git.saont.net - name: Set up Java 17 uses: actions/setup-java@v4 with: distribution: temurin java-version: "17" - name: Set up Flutter uses: subosito/flutter-action@v2 with: channel: stable flutter-version: "3.41.9" cache: true - name: Set up Android SDK uses: android-actions/setup-android@v3 - name: Install Android SDK packages shell: bash run: | set -euo pipefail run_sdkmanager() { set +e yes | sdkmanager "$@" local status="${PIPESTATUS[1]}" set -e return "$status" } run_sdkmanager --install \ "platform-tools" \ "platforms;android-36" \ "build-tools;36.0.0" \ "cmdline-tools;latest" run_sdkmanager --licenses >/dev/null flutter config --android-sdk "$ANDROID_HOME" - name: Check build environment shell: bash run: | set -euo pipefail df -h echo "ANDROID_HOME=$ANDROID_HOME" java -version flutter --version flutter doctor -v - name: Install dependencies run: flutter pub get - name: Restore Android signing files shell: bash env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | set -euo pipefail if [ -z "${ANDROID_KEYSTORE_BASE64:-}" ] || [ -z "${ANDROID_STORE_PASSWORD:-}" ] || [ -z "${ANDROID_KEY_ALIAS:-}" ] || [ -z "${ANDROID_KEY_PASSWORD:-}" ]; then echo "Android signing secrets are required: ANDROID_KEYSTORE_BASE64, ANDROID_STORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD" exit 1 fi printf "%s" "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/app-release.jks { printf "storeFile=../app-release.jks\n" printf "storePassword=%s\n" "$ANDROID_STORE_PASSWORD" printf "keyAlias=%s\n" "$ANDROID_KEY_ALIAS" printf "keyPassword=%s\n" "$ANDROID_KEY_PASSWORD" } > android/key.properties - name: Build Android release APK shell: bash run: | set -euo pipefail rm -rf build dist .dart_tool android/.gradle pkg_dir="$PWD/dist/android" if [ -d "$pkg_dir" ]; then echo "Cleaning old Android build artifacts: $pkg_dir" rm -rf "$pkg_dir" fi echo "Building Flutter Android release APK..." flutter build apk -v --release mkdir -p "$pkg_dir" apk_path="$(find "$PWD/build/app/outputs" -type f -name '*release*.apk' | head -n 1)" if [ -z "$apk_path" ]; then echo "Release APK not found under build/app/outputs" exit 1 fi cp "$apk_path" "$pkg_dir/gongyun-app-release.apk" ls -alh "$pkg_dir/gongyun-app-release.apk" - name: Rename Android APK shell: bash run: | set -euo pipefail apk="dist/android/gongyun-app-release.apk" if [ ! -f "$apk" ]; then echo "Release APK was not found at $apk." exit 1 fi cp "$apk" gongyun-app-release.apk ls -alh gongyun-app-release.apk - name: Upload Android APK artifact uses: actions/upload-artifact@v3 with: name: gongyun-app-release.apk path: gongyun-app-release.apk - name: Publish Gitea release if: startsWith(github.ref, 'refs/tags/') shell: bash env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_API_URL: https://git.saont.net/api/v1 GITEA_OWNER: gongyun GITEA_REPO: app TAG_NAME: ${{ github.ref_name }} TARGET_COMMITISH: ${{ github.sha }} run: | set -euo pipefail if [ -z "${GITEA_TOKEN:-}" ]; then echo "GITEA_TOKEN is required to publish release assets." exit 1 fi releases_url="$GITEA_API_URL/repos/$GITEA_OWNER/$GITEA_REPO/releases" release_by_tag_url="$releases_url/tags/$TAG_NAME" status="$(curl -sS -o release.json -w "%{http_code}" \ -H "Authorization: token $GITEA_TOKEN" \ "$release_by_tag_url")" if [ "$status" = "404" ]; then release_payload="$(python3 - <<'PY' import json import os tag = os.environ["TAG_NAME"] print(json.dumps({ "tag_name": tag, "target_commitish": os.environ.get("TARGET_COMMITISH", ""), "name": tag, "body": f"Automated release for {tag}.", "draft": False, "prerelease": False, })) PY )" status="$(curl -sS -o release.json -w "%{http_code}" \ -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "$release_payload" \ "$releases_url")" fi if [ "$status" != "200" ] && [ "$status" != "201" ]; then echo "Failed to create or load release. HTTP status: $status" cat release.json exit 1 fi release_id="$(python3 - <<'PY' import json with open("release.json", encoding="utf-8") as f: print(json.load(f)["id"]) PY )" assets_url="$releases_url/$release_id/assets" curl -sS -f \ -H "Authorization: token $GITEA_TOKEN" \ "$assets_url" \ -o assets.json python3 - <<'PY' > asset-ids-to-delete.txt import json names = {"gongyun-app-release.apk"} with open("assets.json", encoding="utf-8") as f: for asset in json.load(f): if asset.get("name") in names: print(asset["id"]) PY while IFS= read -r asset_id; do if [ -n "$asset_id" ]; then curl -sS -f \ -X DELETE \ -H "Authorization: token $GITEA_TOKEN" \ "$assets_url/$asset_id" fi done < asset-ids-to-delete.txt curl -sS -f \ -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -F "attachment=@gongyun-app-release.apk" \ "$assets_url?name=gongyun-app-release.apk" - name: Post-build disk cleanup if: always() shell: bash run: | set -euo pipefail rm -rf build dist .dart_tool android/.gradle android/app-release.jks android/key.properties || true if command -v docker >/dev/null 2>&1; then docker system prune -af --volumes || true docker builder prune -af || true fi df -h